线程饥饿表现为线程长期处于waiting或blocked状态却无法获得cpu或锁资源;定位需检查非公平锁竞争、高优先级线程抢占、锁内耗时操作及无界队列滥用等问题。

线程饥饿的典型表现和定位方法
线程饥饿不是抛异常,而是某个或某些线程长期拿不到 CPU 时间片或锁资源,导致任务迟迟不执行。常见现象包括:Thread.getState() 长期为 WAITING 或 BLOCKED,但对应锁的持有者频繁切换、却始终轮不到它;或者用 jstack 看到大量线程卡在 parking to wait for <code>ReentrantLock$Sync 上,而持有锁的线程刚释放就又被其他线程抢走。
定位时优先检查:
- 是否用了非公平锁(默认)且竞争激烈
- 是否存在高优先级线程持续抢占(setPriority 已基本失效,但可排查是否误用)
- 是否有线程在 synchronized 块里做耗时操作(如 I/O、sleep),导致锁持有时间过长
- 是否使用了无界队列 + 持续提交短任务(如 Executors.newCachedThreadPool() 在高并发下可能饿死慢任务)
ReentrantLock 公平锁与非公平锁的核心差异
关键不在“是否排队”,而在「尝试获取锁的时机」:
- 非公平锁(默认):线程调用 lock() 时,**先直接 CAS 尝试抢锁**,抢不到才进队列等待
- 公平锁:线程调用 lock() 时,**跳过 CAS 抢锁,直接入同步队列尾部排队**,唤醒时严格按 FIFO 顺序
这意味着:
- 非公平锁吞吐量更高(减少上下文切换、避免刚唤醒就立刻被插队)
- 公平锁能缓解饥饿,但代价是性能下降 10%–30%,尤其在低争用时更明显
- 公平性只对 lock() 生效;tryLock() 即使在公平锁下也允许插队(因为它本意就是非阻塞尝试)
- 公平锁不能防止“新线程不断涌入导致老线程永远排不上”——如果队列持续有新节点加入,FIFO 本身不保证“某线程最终一定能拿到锁”,只是不人为插队
解决饥饿不能只靠换公平锁
公平锁只是手段之一,实际中常需组合策略:
- 对关键业务线程,用 ReentrantLock(true) 显式启用公平模式
- 避免在锁内做任何阻塞操作(如 socket.read()、Thread.sleep()),必须做则拆出锁外
- 使用带超时的 tryLock(long, TimeUnit),失败后主动降级或记录告警,而非无限等待
- 控制线程池规模,避免 corePoolSize 过小 + maxPoolSize 过大导致部分任务长期滞留在队列头部无法执行
- 若用 synchronized,无法设公平性,只能靠缩小临界区、改用 ReentrantLock 替代
面试时容易被追问的细节
面试官常卡在边界情况:
- “公平锁下,一个线程 lock() 后还没入队,另一个线程刚好释放锁,这时会发生什么?” → 公平锁的 acquire 流程会先检查队列是否为空且当前无持有者,但因为公平策略已启用,它仍会入队(即不会抢),所以释放锁的线程唤醒的是队首,不是这个刚来的
- “为什么 synchronized 没有公平/非公平选项?” → JVM 实现上,其锁膨胀路径(偏向→轻量→重量)不暴露调度策略,且 HotSpot 的 Monitor 实际采用的是类似非公平的策略(唤醒时可能插队)
- “有没有比公平锁更彻底防饥饿的方式?” → 有,比如用 StampedLock 的乐观读 + 悲观写分离,或自己实现带优先级的等待队列(但 JDK 不提供,需谨慎)
真正难的不是选公平还是非公平,而是判断「哪段逻辑必须保响应,哪段可以牺牲一点延迟换吞吐」——这得看监控数据,不是靠背概念能答出来的。










