
java 虚拟线程在 synchronized 块内会被钉住(pinned)而无法卸载,导致失去轻量调度优势;应改用基于 aqs 的非阻塞同步机制(如 reentrantlock 或 semaphore),其底层依赖 cas 和可控的 park/unpark,兼容虚拟线程调度模型。
java 虚拟线程在 synchronized 块内会被钉住(pinned)而无法卸载,导致失去轻量调度优势;应改用基于 aqs 的非阻塞同步机制(如 reentrantlock 或 semaphore),其底层依赖 cas 和可控的 park/unpark,兼容虚拟线程调度模型。
虚拟线程(Virtual Threads)是 Project Loom 的核心特性,旨在实现高吞吐、低开销的并发编程。但其高效调度依赖一个关键前提:线程必须能被及时“卸载”(unmount)到 carrier 线程上,以便让出 CPU 并挂起自身状态。一旦虚拟线程被“钉住”(pinned),它将长期绑定在某个平台线程(carrier)上,丧失调度弹性,退化为传统线程行为。
造成钉住的两大典型场景之一,正是执行 synchronized 块或方法。这并非因为 synchronized 内部使用了自旋锁(实际上 HotSpot 在竞争激烈时会进入操作系统级互斥锁,而非纯用户态自旋),而是因其JVM 实现层面的语义约束:synchronized 是 JVM 指令级原语(monitorenter/monitorexit),其锁状态与线程栈帧深度耦合,且在加锁/解锁过程中可能触发隐式 safepoint 阻塞或不可中断的 native 调用(如 ObjectMonitor::enter 底层调用 pthread_mutex_lock)。这些行为使 JVM 无法安全地在任意时刻暂停并迁移该虚拟线程——即“不可卸载”。
相比之下,java.util.concurrent 包中的现代同步工具(如 ReentrantLock、Semaphore、CountDownLatch)均基于 AbstractQueuedSynchronizer(AQS) 构建。AQS 的核心是无锁(lock-free)的 CAS 操作(通过 Unsafe.compareAndSetInt 或 VarHandle.compareAndSet 实现),在无竞争时完全不阻塞线程;仅在需要挂起等待者时,才调用 LockSupport.park() —— 这一方法虽为 native,但已被 Loom 显式适配:JVM 知道 park() 是协作式挂起点,可在调用前完成虚拟线程状态快照与卸载,后续通过 unpark() 触发安全唤醒与重挂载。
以下是一个对比示例:
// ❌ 危险:synchronized 会导致虚拟线程钉住
void badExample() {
synchronized (lock) {
// 执行 I/O 或其他可能耗时操作
doSomethingExpensive();
}
}
// ✅ 推荐:使用 Semaphore 实现可卸载的临界区保护
private final Semaphore semaphore = new Semaphore(1);
void goodExample() throws InterruptedException {
semaphore.acquire(); // 可卸载:若需等待,虚拟线程将被挂起并释放 carrier
try {
doSomethingExpensive();
} finally {
semaphore.release();
}
}
⚠️ 注意事项:
-
ReentrantLock语义更接近synchronized(支持可重入、条件变量),但同样需显式lock()/unlock(),且必须配合 try-finally 或 try-with-resources 使用,否则易引发死锁; -
Semaphore(1)是最直接的替代方案,语义清晰、无重入开销,适合多数临界区场景; - 即便使用 AQS 工具,也应避免在
acquire()后执行长时间阻塞操作(如Thread.sleep()或阻塞 I/O),而应优先采用CompletableFuture+ 异步 I/O(如 NIO、VirtualThread-aware HttpClient); -
synchronized并非绝对禁用——若临界区极短(纳秒级)、无 I/O、无可能阻塞调用,且不处于高并发路径,其开销仍可接受;但在面向虚拟线程的设计中,应默认视为“钉住风险源”并主动规避。
总结而言,虚拟线程的调度自由度建立在“可预测的挂起点”之上。synchronized 因其实现封闭性与 JVM 底层耦合,破坏了这一前提;而基于 CAS 与显式 park/unpark 的并发工具,则提供了与 Loom 调度器协同工作的契约接口。拥抱 java.util.concurrent,不仅是性能升级,更是对结构化、可推理的异步编程范式的践行。










