lock和synchronized加锁操作本身都不进内核态,仅在竞争激烈导致线程挂起时(如locksupport.park()或重量级锁等待),才由jvm触发系统调用进入内核态。

Java中Lock和synchronized到底谁进内核态?
都不进。这是最常见的误解——synchronized 和 ReentrantLock 在绝大多数情况下全程运行在用户态,JVM 通过 CAS、自旋、队列等纯软件机制实现锁逻辑,不触发系统调用,更不进入内核态。
只有当线程竞争激烈、自旋失败后被挂起时,JVM 才会调用 pthread_cond_wait(Linux)或类似系统原语,此时线程才真正陷入内核态等待。但这属于“锁等待”的代价,不是“加锁操作”本身进内核。
-
synchronized的 monitor enter/exit 默认走快速路径:CAS 修改对象头中的 mark word,无竞争时几乎零开销 -
ReentrantLock.lock()底层也是基于Unsafe.compareAndSwapInt操作 AQS 的 state 字段 - 真正进内核的节点是:AQS 中
LockSupport.park()被调用时(线程阻塞),而非 lock() 调用那一刻
为什么synchronized没有显式unlock却不会内存泄漏?
因为 synchronized 是 JVM 层语法糖,编译后由 monitorenter / monitorexit 字节码指令保证配对执行,哪怕发生异常,JVM 也会在对应异常表中插入 monitorexit 指令。
而 ReentrantLock 是普通 Java 类,lock() 和 unlock() 完全对称,必须手动配对,漏写 unlock() 就真会死锁或资源泄露。
立即学习“Java免费学习笔记(深入)”;
- 反编译含
synchronized的方法,能看到finally块里重复出现monitorexit -
ReentrantLock的unlock()必须放在finally中,否则 try 块抛异常就跳过了 - JVM 不负责回收
ReentrantLock占用的 AQS Node,但会自动清理synchronized关联的 monitor(对象头复位 + monitor 复用)
LockSupport.park() 和 synchronized 等待的本质区别
表面都是“线程暂停”,但背后调度粒度和语义完全不同:LockSupport.park() 是线程级阻塞,由 JVM 直接委托给 OS;而 synchronized 的等待,在轻量级锁膨胀为重量级锁前,压根不 park,只自旋。
换句话说:synchronized 的“等”可能是空转 CPU,Lock 的“等”大概率是交出 CPU 时间片。
- 自旋阈值默认是 10 次(可通过
-XX:PreBlockSpin调整),超过才调用park() -
ReentrantLock默认不自旋(除非用new ReentrantLock(true)开启公平模式下的有限重试) - 频繁 park/unpark 会触发线程状态切换开销,比单纯 CAS 自旋更重——这也是为何高并发短临界区场景下 synchronized 反而更快
排查“锁没释放”问题时,别只盯着 unlock() 调用
常见误判是:看到 unlock() 写了,就认为没问题。实际上,如果 lock() 和 unlock() 不在同一个线程执行(比如跨线程传递锁对象)、或 unlock() 被异常吞掉(没写 finally)、或锁对象被多次 lock() 却只 unlock() 一次,都会导致持有数不归零。
而 synchronized 天然规避了前两种情况,只可能因嵌套过深+异常打断导致 monitor 未完全退出(极罕见,且 JVM 有兜底)。
- 用 jstack 查
java.lang.Thread.State: BLOCKED on java.lang.Object@...表示还在等 monitor;WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@...才是 Lock 的条件等待 -
jcmd <pid> VM.native_memory summary</pid>看不到锁内存占用,但jstack能暴露谁卡在哪个锁上 - AQS 的
state值非 0 且线程不活跃,基本可断定是unlock()缺失或错配










