当前位置: 首页 > news >正文

《锁饥饿:能抢却抢不到的并发困境》

前言

在多线程编程中,锁饥饿(Lock Starvation)是一个容易被忽视但后果严重的并发问题。很多开发者听说过“死锁”,但对“饥饿”的理解往往停留在“线程拿不到锁”的模糊概念上。本文将用生动的比喻、可运行的代码以及底层原理分析,带你彻底搞懂锁饥饿的本质、产生原因及解决方案,让你在使用ReentrantLock等锁时不再踩坑。


一、锁饥饿 vs 死锁,别再傻傻分不清

在正式讲解之前,我们先明确两个容易混淆的概念:

概念现象本质
死锁线程互相持有对方需要的锁,形成循环等待互相等,根本没法抢锁
锁饥饿线程一直有机会抢锁,但总被其他线程抢先,导致长期无法执行能抢,但永远抢不到

简单来说,死锁是“大家都动不了”,饥饿是“有人一直在跑,有人永远轮不到”。


二、生活化比喻:从银行办业务秒懂锁饥饿

想象你去银行办理业务,有两种不同的叫号机制:

  1. 公平锁场景:银行严格按取号顺序叫号。即使你是普通客户,VIP客户来了也得取号排队——先到的人一定先办业务,不会有人被“饿死”。
  2. 非公平锁场景:银行允许新进门的客户直接冲到窗口(如果窗口刚好空闲)。
    - 你排队等了1小时,终于轮到你了,窗口刚空,突然冲进来一个新客户直接办业务——你被截胡了。
    - 如果新客户源源不断,每次窗口一空就有人冲上去,那么你永远轮不到,这就是锁饥饿

在这个比喻中:

  • 你 =等待队列的队首线程(最早开始等待的线程)
  • 新客户 =新尝试抢锁的线程
  • 窗口 =
  • 办业务 =拿到锁执行代码
  • 锁饥饿 =队首线程被新线程持续截胡,永远拿不到锁

三、代码复现:用ReentrantLock直观演示锁饥饿

下面我们用ReentrantLock的非公平模式(默认)来复现锁饥饿场景。代码中有一个“受害者线程”最先开始等待锁,随后启动10个“插队线程”疯狂抢锁,看看会发生什么。

import java.util.concurrent.locks.ReentrantLock; public class LockStarvationDemo { // 非公平锁(默认):新线程可以插队 private static final ReentrantLock unfairLock = new ReentrantLock(false); public static void main(String[] args) { // 受害者线程:第一个来等待锁,却一直被插队 Thread victimThread = new Thread(() -> { int count = 0; while (true) { unfairLock.lock(); try { // 如果能拿到锁,打印次数 System.out.println("受害者线程拿到锁!次数:" + (++count)); Thread.sleep(1); // 模拟业务操作,很快释放锁 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { unfairLock.unlock(); } } }, "受害者线程"); victimThread.start(); // 让受害者线程先启动,确保它最先进入等待队列 try { Thread.sleep(10); } catch (InterruptedException e) { } // 启动10个插队线程,持续抢锁 for (int i = 1; i <= 10; i++) { int index = i; Thread insertThread = new Thread(() -> { while (true) { unfairLock.lock(); try { System.out.println("插队线程" + index + "抢到锁"); Thread.sleep(1); // 同样快速释放 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { unfairLock.unlock(); } } }, "插队线程" + i); insertThread.start(); } } }

运行结果观察(可能因环境略有不同):

  • 控制台几乎被“插队线程X抢到锁”刷屏。
  • “受害者线程拿到锁!次数:1”可能偶尔出现一两次,之后几乎不再出现。
  • 即使程序运行很久,受害者线程也很难再拿到锁——这就是典型的锁饥饿。

四、核心拆解:为什么新线程能一直截胡队首线程?

可能会疑惑:那假如有10个插队线程,肯定只有一个可以抢到,其他不应该变为等待线程吗?为什么上述结果中其他9个线程不是新线程了,还能一直插队?

4.1 非公平锁的“插队规则”

非公平锁的抢锁逻辑是:

  • 当锁被释放时,等待队列中的队首线程(受害者)被唤醒,准备参与竞争。
  • 但与此同时,新来的线程(或者刚释放锁的线程)可以直接尝试CAS抢锁,不需要排队。
  • 如果新线程抢锁成功,队首线程的唤醒就白费了,它只能再次进入等待。

4.2 插队线程的循环逻辑:为什么它们几乎不进队列?

仔细观察插队线程的行为:

- 抢锁 → 拿到锁 → 执行1ms → 释放锁 → 立刻再次抢锁(又变成“新线程”)

它们永远处于“抢锁成功→释放→立刻再抢”的循环中。
- 在非公平锁下,这些线程抢锁的成功率极高,因为锁刚释放,它们立刻尝试,几乎每次都能成功。
-只有抢锁失败的线程才会进入等待队列。而这些线程很少失败,所以它们几乎不会进入队列,永远是“新线程”身份。

用银行比喻再细化:这10个插队线程永远是“窗口旁的新客户”——办完业务后不排队,就在窗口边等着,窗口一空又立刻冲上去,全程没有机会进入等待队列,自然永远是“能插队的新线程”。

4.3 路径+时序优势:新线程抢赢队首线程的底层原因

即使偶尔有插队线程抢锁失败,队首线程也往往抢不过其他插队线程,原因有两个:

  1. 路径优势
    - 新线程抢锁只需在用户态执行CAS操作,非常快。
    - 队首线程被唤醒需要从内核态切换回用户态(涉及上下文切换),存在延迟。
  2. 时序优势
    - 新线程是“主动轮询”,可以持续检测锁状态。
    - 队首线程是“被动唤醒”,必须等待操作系统调度,往往慢一拍。

即使有多个插队线程竞争,失败的线程也会进入短暂的自旋等待(忙等),在用户态反复尝试CAS,不会立刻进入等待队列。只有当自旋次数超过阈值,才会进入内核态等待队列。但因为锁释放极快,它们很快又被唤醒,继续自旋抢锁——所以很难成为“长期等待线程”。


五、解决锁饥饿:从非公平到公平,代价与取舍

解决锁饥饿最直接的方法就是使用公平锁。只需将锁的创建改为:

private static final ReentrantLock fairLock = new ReentrantLock(true);

公平锁强制新线程必须排队,不允许插队。修改后再次运行代码,你会发现:

  • 受害者线程和插队线程会交替拿到锁,每个线程都能公平执行。
  • 不会再出现某个线程长期得不到执行的情况。

公平锁的代价

公平锁虽然解决了饥饿,但付出了性能代价:

  • 需要维护一个FIFO队列,增加了锁管理的开销。
  • 吞吐量通常比非公平锁低,因为严格排队可能降低并发度。

其他缓解措施

除了公平锁,还可以通过控制新线程的创建速度、增加线程的休眠时间等方式降低插队频率,但无法从根本上杜绝饥饿。如果系统对实时性要求高,建议直接使用公平锁


六、总结:三句话牢记锁饥饿

  1. 锁饥饿只发生在非公平锁:新线程持续截胡等待队列的队首线程。
  2. 饥饿≠死锁:饥饿是“能抢但永远抢不到”,死锁是“互相等,根本没法抢”。
  3. 解决饥饿的核心:用公平锁(放弃插队),或限制新线程的抢锁频率。
http://www.jsqmd.com/news/453846/

相关文章:

  • Timestamp.cc和Timestamp.h文件分析
  • 2026 低压高低氧舱品牌推荐:优质厂家、靠谱公司、实力对比全解析 - 品牌推荐大师1
  • 部委政务安全智能运营未来五年发展规划与工作思路
  • 47.102.113.21
  • AI coding上手之OpenClaw快速上手
  • 【刘二大人】《PyTorch深度学习实践》——反向传播代码(自用)
  • 导师推荐 9个AI论文软件:自考毕业论文+开题报告写作全测评
  • 解读代码Dftpav-main(3.1规划核心traj_server_ros.cpp26.3.8)
  • Linux:网络编程-基于HTTP协议的天气预报查询系统开发详解
  • Kafka自动提交把消息吃了:一次“已提交未处理”+重平衡导致丢数和爆堆积
  • 把 AI助手搬进飞书!OpenClaw接入完整指南
  • 2026广州GEO优化公司排名TOP5|本地实力派盘点,亚森SEO稳居榜首!
  • 周红伟:2026年OpenClaw最佳实践:一键部署+免费API配置+集成8大股票分析Skills及避坑指南
  • matlab麻雀搜索算法(SSA)优化BP神经网络,权值和阈值,一个压缩包共三个文件,包括有数...
  • 深度学习在财务报表舞弊识别中的应用:构建一个智能审计助手
  • Rokid UXR 的手势追踪虚拟中更真实的手实战开发【含 工程源码 和 最终完成APK】
  • 开发者的临时文件自动化工具:提升效率与系统整洁度的关键方案
  • 别只当它是管家,RT-Thread 会自己生长
  • 权威解读:企业合作政策如何让非科班生通过国内AI认证实现“弯道超车”?
  • 2026年房山及燕山地区装修套餐全解析:五大优质服务商深度推荐 - 品牌2026
  • openclaw gateway status报错且gate无法正常运行解决办法
  • 无数绘画测试!Nano Banana 2 vs GPT Image 1.5,谁才是最厉害的模型
  • LeetCode-35.搜索插入位置
  • 基于javaweb的作业智能推荐系统的设计与实现
  • 2026超纯水机厂家推荐:进口与国产品牌实力对比 - 品牌推荐大师
  • 光学神经网络:进展与挑战(Optical Neural Networks: Progress and Challenges)
  • 如何本地部署大模型(以PaddleOCR-VL-1.5为例)
  • 2026年房山环保家装公司怎么选?五家实力装企深度解析 - 品牌2026
  • Gemini 3.1 Flash Image Preview (Nano Banana 2) 深度技术评测与极速接入指南
  • 2026最新25万级SUV智驾领先双能源车型推荐!权威榜单发布 - 十大品牌榜