reentrantlock 本身不触发 pinning,因其底层使用 loom-aware 的 unsafe.park()/unpark(),而 synchronized 依赖 jvm monitor 会导致虚拟线程“焊死”;但错误用法(如 condition.await 或嵌套 synchronized)仍会引发 pinning,需严格控制临界区、避免阻塞操作并合理使用 trylock。

能解决,但不是简单替换就完事。ReentrantLock 本身不触发 Pinning,关键在于用法是否得当——它只在 lock() 和 unlock() 这段逻辑里保持轻量调度,一旦混入 synchronized 或阻塞等待(比如 Condition.await),就会重新掉进 Pinning 坑里。
明确 ReentrantLock 不 Pin 的底层原因
synchronized 依赖 JVM monitor,owner 字段直接存平台线程指针,虚拟线程进去就“焊死”;而 ReentrantLock 底层调用 Unsafe.park()/unpark(),这两个操作是 Loom-aware 的,JVM 可在挂起时安全卸载虚拟线程,让载体线程去跑别的任务。
- ✅ 正确前提:lock 实例必须是非公平锁(默认)或显式指定,且未启用 fairness=true(公平模式会增加调度开销,但不导致 Pinning)
- ❌ 错误认知:以为只要用了 ReentrantLock 就自动规避 Pinning —— 实际上 lock.newCondition().await() 仍会 Pin,和 Object.wait() 同类
- ⚠️ 注意边界:lock.tryLock() 成功后若又嵌套 synchronized(this) { ... },等于前门拒狼、后门引虎
重构时必须守住的三条红线
不是语法替换,而是语义重设计。重点不在“怎么加锁”,而在“锁什么、锁多久、失败怎么办”。
- 只锁真正共享且需原子更新的字段,比如 AtomicInteger.getAndIncrement() 能搞定的,就别上锁
- 临界区严格控制在微秒级:避免包含 I/O、网络调用、日志打印、JSON 序列化等任何可能耗时操作
- 永远不用 lock.lockInterruptibly() 或 await();改用 tryLock(timeout, unit),超时即放弃并主动 yield() 或重试
典型重构示例:从 synchronized 方法到协作式锁
原写法(Pinning 高风险):
public synchronized void updateBalance(long delta) {
this.balance += delta; // 看似简单,但整个方法体都被 Pin
log.info("balance updated: {}", balance); // 日志耗时,放大 Pinning 影响
}
重构后(无 Pinning,可调度):
private final ReentrantLock balanceLock = new ReentrantLock();
private final AtomicLong balance = new AtomicLong();
public boolean tryUpdateBalance(long delta) {
if (balanceLock.tryLock(10, TimeUnit.MILLISECONDS)) {
try {
balance.addAndGet(delta);
return true;
} finally {
balanceLock.unlock();
}
}
return false; // 失败不阻塞,由调用方决定重试或降级
}
- log 放到锁外,失败时也可记录“更新尝试失败”
- AtomicLong 替代了锁内纯数值更新,进一步缩小锁范围
- 调用方可配合虚拟线程特性做轻量重试:VirtualThread.of(() -> { while (!tryUpdate...) Thread.onSpinWait(); })
遗留代码无法动?用平台线程池兜底
如果第三方 SDK 或老模块强制使用 synchronized,又不能改源码,就别硬刚。把高争用的同步逻辑隔离出去:
- 创建专用固定大小的平台线程池(如 Executors.newFixedThreadPool(4))
- 将涉及 synchronized 的调用 submit 过去,让虚拟线程只负责发起和结果聚合
- 这样 Pinning 被限制在少数平台线程上,不影响整体虚拟线程调度效率
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











