公平锁通过fifo队列减少线程饥饿,但无法消除;需配合限持锁时间、避免无意义重入、监控队列长度及优先使用带超时的trylock()等设计策略。

ReentrantLock 的公平锁能缓解线程饥饿,但不能完全消除;关键在于理解其调度逻辑,并配合合理的设计习惯。
公平锁如何减少饥饿
公平锁(new ReentrantLock(true))让线程按进入等待队列的顺序获取锁,避免新线程“插队”抢占刚释放的锁。相比非公平锁(默认),它降低了高竞争下某些线程长期得不到执行的可能性。
说明:非公平锁允许刚唤醒或新来的线程直接尝试抢锁,可能反复成功,导致排在 AQS 队列尾部的线程迟迟无法出队——这就是典型的线程饥饿现象。公平锁强制遵循 FIFO,使每个等待线程都有确定的“轮次”。
公平锁不是万能药:仍需注意的边界情况
即使开启公平模式,以下情形仍可能导致事实上的饥饿:
- 持有锁的线程执行时间过长(如含 I/O、死循环、复杂计算),其他线程只能干等,公平性只管排队顺序,不干预单次持锁时长
- 部分线程频繁调用 lockInterruptibly() 或超时获取(tryLock(long, TimeUnit)),失败后立即重试,形成“伪活跃”,挤占真实等待者的调度机会
- 锁被嵌套使用或与其它同步机制(如 synchronized、Semaphore)混用,公平性仅作用于该 ReentrantLock 实例本身
搭配使用的实用建议
要真正抑制饥饿,需结合公平锁做主动设计:
- 限制单次持锁时间:把大任务拆解,避免在 lock 块内做耗时操作;必要时用 tryLock() + 退避策略(如指数睡眠)替代无条件阻塞
- 避免无意义重入:检查是否过度使用 lock() / unlock(),尤其在循环或递归中;重复加锁不改变公平性,反而增加队列压力
- 监控等待队列长度:通过 getQueueLength() 定期采样,若持续增长,说明吞吐不足,应优化业务逻辑或扩容线程池,而非仅依赖锁策略
- 慎用 lock() 代替 tryLock():对响应性要求高的场景(如实时服务),优先用带超时的获取方式,主动放弃而非无限等待
一个简化的对比示例
假设有 5 个线程争抢同一把锁:
- 非公平模式下,线程 T5 可能在 T1~T4 还在队列中时,恰好遇到锁释放,直接抢到,T1 可能连续错过多次
- 公平模式下,T1 入队最早,下次锁释放时必定由 T1 获取(除非它被中断或超时),T2~T5 严格按到达顺序依次执行
注意:这个“必定”前提是这些线程都处于 WAITING 状态且未被中断;若有线程中途取消等待(如调用 Thread.interrupt()),队列会调整,但剩余节点仍保持原有相对顺序。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











