商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 如何在 Java 中通过 ReentrantLock 实现公平锁以防止高并发下的线程饥饿

如何在 Java 中通过 ReentrantLock 实现公平锁以防止高并发下的线程饥饿

  发布于2026-07-11 阅读(0)

扫一扫,手机访问

是的,ReentrantLock构造函数传true确实能开启公平锁,让线程按FIFO顺序竞争锁。但严格来说,这个公平性只覆盖了阻塞后重新竞争的场景。换句话说,它并不能解决因持锁时间过长、未及时释放、或者混用不同锁实例所导致的饥饿问题。而且,公平锁的性能损耗相当可观,这一点必须心里有数。

如何在 Ja va 中通过 ReentrantLock 实现公平锁以防止高并发下的线程饥饿

ReentrantLock 构造函数里传 true 就是公平锁?

很多人以为,ReentrantLock的构造函数里传个true就能万事大吉 —— "看好了,我要公平模式了!" —— 然后等着线程们乖乖按顺序排队领锁对吧?这么说没有问题但不能算完整答案。

ReentrantLock默认是非公平锁,true的含义是把它的候队旗换成 FIFO 模式而已:

 ReentrantLock lock = new ReentrantLock(true);

`true`版本会让它们老老实实排队吗?答案是肯定的 —— 仅限于它们已经在等待队列里趴着的状态下。线程刚调用`lock()`但还没阻塞时,依然有可能嗅到躲在锁刚释放那一瞬的机会,抢先插进去占上几秒钟(这点和非公平锁如出一辙)。不过老规矩:一旦真的卡进队列,那就老老实实先来后到。

为什么开了公平锁,还是有线程长时间拿不到锁?

有些同学大概会问:我都把标志设成`true`了,咋还是有线程像多余的外卖箱一样在队列里站岗?这背后其实藏着几个常见的踩坑现场:

  • `finally`块里漏了`unlock()`,这把锁就成了一次性的 —— 谁拿到谁永占,队里其他人只有等着哭的份儿;
  • 临界区分块太大:有人在锁里做IO请求、远程调用或者跑大循环,那这把锁相当于被一个线程拴在水泥柱上5秒钟,队列后面的人只能干瞪着o(一︿一+)o;
  • 没有真正形成锁竞争:最经典的就是每个对象自己`new`一把`ReentrantLock`,各锁其主,根本不对同一个东西争,那你传一百万个`true`也只是新闻标题上的字 =_=;
  • `tryLock()`配合无限轮询、没有控制退避策略 —— 一个接一个地白忙活,后台的CPU风扇直接被劝退。

公平锁的性能代价到底有多大?

老话说得好:鱼与熊掌不可兼得,给这么“讲道理”的锁多付出的代价还真不小。拿数据来说的话,公平锁会显著压扁整体吞吐能力,尤其是在高并发搏杀下——

  • 每次`lock()`它都要瞄一眼自己的同步队列:里面人空不空?我现在该不该去排最后一位?这一套动作下来,单次的开销就能比非公平版高出好2~5倍;
  • 上下文切换自然会变多:排队线程里总有些被挂起来又被叫醒,CPU缓存热乎乎的亲和性就这么没了;
  • JVM对公平锁根本不做锁消除、锁粗化之类的调度优化,所以逃逸分析对这个场景也基本失效;
  • 我们在一台16核机器上做过压测:100个线程抢一把公平锁,结果吞吐量竟然还不到非公平版本的三分之一。看吧,这不是无病呻吟……

真要防饥饿,光靠公平锁远远不够

一口气说透:公平锁只是解决了“谁先到谁先上”的排队贞操问题,但它对付不了线程饥饿的根本病因。

如果某个线程手持锁5秒不放,哪怕它是队首透明人,后面排队的100个线程也只能干等5秒 —— 这根本不关调度的事,是设计本身有坑。

  • 永远不要用`lock()`作无限等待。优先考虑`tryLock(long, TimeUnit)`,配合适当的指数退避策略让人家歇歇;
  • 真正需要防饥饿的场景,比如任务调度、资源额度分配、限流熔断,这时候该上优先级队列、权重分配等技术,而非靠锁的公平性来完成这个使命;
  • 邪门的是,有些JDK版本(比如 8u292+)对公平锁的队列唤醒存在微妙的偏差,极端情况下甚至可能跳过一个现成的队列线程。公平锁?别当作强实时方案来押注。

所以一句话:公平锁暴露出的只是“谁先拿谁先上”的单薄语义,它会把代码里任何低效或遗漏的毛病毫不留情地摊在太阳底下。而线程饥饿真正的根因,还是藏在锁粒度、临界区长度以及错误恢复逻辑中,不是那个构造函数的传参就能转得了向的。

本文转载于:https://www.php.cn/faq/2381835.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注