低优先级线程在synchronized中总等不到锁,是因为JVM锁机制不保证获取顺序,且线程优先级对锁竞争无实质影响;公平锁仅缓解排队饥饿,真正防饥饿需结合超时、拆分临界区、无锁结构与监控。

为什么低优先级线程在synchronized里总等不到锁
Java默认的synchronized和ReentrantLock(非公平模式)不保证线程获取锁的顺序,调度器可能反复选中高优先级或刚唤醒的线程,导致低优先级线程长期被跳过——这不是bug,是设计使然。JVM线程优先级本身对锁竞争几乎没有影响,Thread.setPriority()主要作用于操作系统线程调度,而锁的排队逻辑由JVM内部队列控制。
- 现象:一个
while(true)循环里调用synchronized方法的低优先级线程,CPU占用接近0,日志几乎不输出 - 根本原因:锁释放后,JVM通常把锁直接交给下一个尝试获取的线程(可能刚被唤醒、刚从wait返回),而不是按等待时间排序
- 别依赖
setPriority()来“抢”锁——它既不提升抢锁成功率,还可能因OS差异引发更不可控的行为
ReentrantLock(true)真能解决饥饿吗
能缓解,但不是银弹。ReentrantLock构造时传true启用公平模式,会让锁按线程进入等待队列的顺序分配,避免新到线程插队。但它只约束“等待队列内的顺序”,不解决线程根本进不了队列的问题。
- 公平锁代价明显:每次获取锁都要检查队列头,性能比非公平锁低20%~50%,尤其在高争用场景
- 仍可能饥饿:如果某线程频繁调用
tryLock()或在锁外做大量计算,它可能永远不进等待队列,而其他线程一直在队列里“排队等死” - 注意:公平性仅对
lock()生效;tryLock()即使在公平锁下也不排队,直接失败返回false
真正防饥饿的实用组合策略
单靠锁机制无法根治饥饿,得配合线程行为约束和资源管理。重点不是“让低优先级线程抢到锁”,而是“不让它无限期等待”。
- 用
lock.tryLock(timeout, TimeUnit)代替无条件lock(),超时后做退避(如Thread.sleep(10))或降级处理 - 把长耗时操作移出临界区:比如读取配置、拼接字符串、IO等待这些别放在
synchronized块里 - 考虑用
java.util.concurrent里的无锁结构替代:高频读写计数器用AtomicInteger,状态标志用AtomicBoolean,比锁+等待更可靠 - 如果必须强顺序,用
LinkedBlockingQueue做任务队列,由单一线程消费,彻底消除锁竞争
监控和验证饥饿是否真实发生
别凭感觉判断“线程饿死了”,先看证据。JVM自带工具足够定位,不用上Arthas也能确认。
立即学习“Java免费学习笔记(深入)”;
- 用
jstack <pid>抓线程快照,搜索WAITING (on object monitor)或BLOCKED (on object synchronization monitor),看同一低优先级线程是否连续多次出现在等待列表头部却未获得锁 - 在锁入口加简单计数:
private static final AtomicInteger waitCount = new AtomicInteger();,在synchronized块外自增,在块内清零,通过JMX或日志观察该值是否持续飙升 - 注意:GC停顿、长时间
System.out.println()、或锁内调用wait()都可能伪装成饥饿,先排除这些干扰项
饥饿的本质是资源分配策略与线程行为不匹配。公平锁只是调整了排队规则,真正的解法藏在“减少等待必要性”和“给等待设底线”里——超时、拆分、替换、监控,四者缺一不可。线程不会说话,但jstack和计数器会告诉你它到底卡在哪一步。










