synchronized底层使用JVM Monitor锁,默认非公平,锁释放后任何线程均可抢占,导致等待久的老线程反复抢不到锁而饥饿;ReentrantLock可通过true参数启用公平模式实现FIFO排队,避免饥饿但吞吐量下降。

Java 中 synchronized 关键字底层使用的是 **JVM 内置的 Monitor 锁(也叫 intrinsic lock)**,其等待队列默认采用 **非公平策略**,这可能导致“老员工线程饥饿”——即某个线程在锁释放后反复抢不到锁,长时间阻塞,哪怕它已经等了很久。
非公平锁怎么运作?
非公平不等于“随机”,而是指:每当锁被释放,**任何线程(包括刚唤醒的、刚新建的、甚至还没进队列的)都可以直接尝试抢占锁**。如果抢成功了,就跳过排队;失败了,才乖乖入队等待。
- 新线程可能比已等待多轮的老线程更快获得 CPU 时间片,从而抢先 CAS 抢到锁
- JVM 的 Monitor 实现(如 HotSpot 的 ObjectMonitor)没有强制按 FIFO 唤醒,唤醒顺序由操作系统线程调度和竞争时序共同决定
- 没有“等待时间戳”或“队列位置”作为优先级依据,老线程不会被特殊照顾
为什么会出现“老员工饥饿”?
所谓“老员工”,是指早早就进入 Monitor 的 EntryList(或 _WaitSet 唤醒后转入的队列)并持续等待的线程。但非公平性下:
- 每次 unlock 后,总有新线程(比如高频请求的 Web 请求线程)立刻 park/unpark 或直接 CAS 尝试获取锁
- 老线程还在从阻塞态被唤醒、上下文切换、重新竞争的过程中,锁已被抢走
- 若系统并发高、临界区短、锁释放频繁,这种“插队”会持续发生,导致某个线程数秒甚至数分钟都拿不到锁
和 ReentrantLock 公平锁对比更清楚
ReentrantLock fair = new ReentrantLock(true) 显式启用公平模式后:
立即学习“Java免费学习笔记(深入)”;
- 线程必须排队,按入队顺序依次唤醒
- 即使有新线程来,也必须排在队尾,不能插队
- 能基本保证“先到先得”,避免饥饿,但吞吐量下降(频繁检查队列+更多线程唤醒开销)
而 synchronized 没有公平开关,永远是非公平的——这是 JVM 为吞吐量做的取舍。
实际中怎么判断是不是饥饿?
不能只看“卡住了”,要确认是否真因锁竞争导致:
- 用
jstack <pid>查看线程堆栈,找大量线程停在java.lang.Object.wait()或parking to wait for <0x...>,且某个线程长期处于BLOCKED状态 - 结合 JFR(Java Flight Recorder)或 Arthas 的
thread -b,观察该线程是否反复在 entry list 头部却总抢锁失败 - 注意排除其他原因:比如临界区里调用了慢 IO、死循环、或被其他锁阻塞(锁嵌套)
不复杂但容易忽略:synchronized 的非公平是设计使然,不是 bug。真有强顺序要求,应换用显式公平锁或重构逻辑减少锁粒度。


















