并发编程笔记
多个线程互相持有对方需要的锁,且都不主动释放锁,导致所有线程陷入无限等待,无法继续执行的状态,就是死锁。循环等待、持有并等待、不可剥夺、互斥(四大必要条件)。Java 并发编程,上层是线程、锁、关键字的使用,底层是 CPU 缓存、操作系统调度、硬件总线的限制。搞懂线程生命周期、上下文切换,理解多线程调度逻辑;分清区别,避开锁使用误区;理解 CPU 缓存、内存屏障,明白并发安全问题的硬件根源;掌握j
一、基础概念:并发 & 并行 & 线程调度
1. 并发与并行区别
- 并发:多线程争抢资源,单核 CPU 通过时间片切换执行,同一时刻只有一个线程运行;
- 并行:多核 CPU,多线程不抢占资源,同时执行。
2. 线程完整生命周期
text
新建 → 就绪 → 运行 → 阻塞 → 完毕
- 新建:线程对象初始化;
- 就绪:调用
start()后,进入操作系统就绪队列,等待 CPU 调度- 就绪队列分两种:
- 公平队列:先进先出,性能损耗大;
- 非公平队列:短任务优先、人机交互频繁线程优先级更高;
- 核心结论:任何线程创建后不会立刻执行,必须进入就绪态等待 CPU 选中。
- 就绪队列分两种:
- 运行:CPU 分配时间片,线程开始执行;
- 阻塞:竞争资源失败、休眠、等待时进入阻塞队列,不再消耗 CPU;
- 完毕:线程执行结束。
3. 上下文切换
操作系统随机调度线程,切换时会产生上下文切换: CPU 执行完线程后,保存当前执行信息,下次继续执行。
- 时间损耗:毫秒级,单次切换约
10ms至12ms; - 适用场景:
- IO 密集型(网络、磁盘读写):适合多线程;
- CPU 密集型:单核下单线程最快;
- 优化方向:减少上下文切换,采用无锁并发、CAS 算法、使用最少线程。
4. 线程执行特性
- 单线程:代码从上往下顺序执行;
- 多线程:线程执行顺序不确定,由操作系统调度决定;
join():调用线程执行完毕后,后续代码才会执行,控制线程执行顺序。
二、Java 锁核心机制
1. synchronized 加锁规则
- 只能对引用类型加锁,基本数据类型无法加锁;
- 锁释放时机:只有
synchronized代码块 / 方法执行完毕,才会释放锁; - 核心特性:线程让出 CPU,锁不会释放。
2. sleep () 和 wait () 核心区别
表格
| 方法 | 是否让出 CPU | 是否释放锁 | 归属类 |
|---|---|---|---|
| sleep() | 让出 CPU | ❌ 不释放锁 | Thread |
| wait() | 让出 CPU | ✅ 释放锁 | Object |
sleep(2000):线程休眠 2s,进入阻塞队列,休眠期间持有锁,其他线程无法访问;- 死锁成因:两个线程互相持有对方需要的锁,同时进入阻塞队列,无限等待。
3. volatile 轻量级锁
- 作用:只保证读安全、保证可见性,线程能读取最新变量值;
- 缺陷:不保证原子性,写操作会相互覆盖,计算结果不准确;
- 对比:
synchronized是重量级锁,读写都加锁,保证原子性 + 可见性,安全性更高。
三、硬件底层原理:并发问题的根源
1. 总线竞争问题
计算机总线同一时刻只能传输一个指令,单核时代内存同一时刻只能被一个指令操作。 多核 CPU 优化:增加高速缓存,每个核心独立缓存,提升利用率,但带来缓存一致性的并发问题。
2. 高速缓存 & 内存屏障
- 缓存行:大小为 64 字节,一个缓存行不能同时读写;
- 内存屏障:标记内存区域,控制线程读写权限,解决缓存数据不一致;
- 缓存读取规则:CPU 先查高速缓存,无数据再查内存
- 写命中:数据写入高速缓存;
- 写缺失:数据直接写入内存。
3. 原子操作
一系列操作要么全部成功,要么全部失败,中间出错则全部回滚,是并发安全的底层基础。
四、重点补充:死锁
1. 死锁定义
多个线程互相持有对方需要的锁,且都不主动释放锁,导致所有线程陷入无限等待,无法继续执行的状态,就是死锁。
核心特点:循环等待、持有并等待、不可剥夺、互斥(四大必要条件)。
2. 死锁四大必要条件
只要破坏其中任意一个条件,就能避免死锁:
-
互斥条件:锁资源只能被一个线程持有(无法破坏,锁的核心特性);
-
持有并等待条件:线程持有一个锁后,又去等待另一个锁(可破坏:一次性获取所有需要的锁);
-
不可剥夺条件:线程持有锁后,不能被其他线程强制剥夺(可破坏:超时自动释放锁);
-
循环等待条件:多个线程形成“你等我锁,我等你锁”的循环(可破坏:按固定顺序获取锁)。
3. 线上死锁排查
死锁排查核心还是依赖 jstack 工具,步骤如下(和线程排查联动):
-
第一步:查看Java进程号(找到目标进程)
ps -ef | grep java -
第二步:导出线程dump文件
sudo -u admin jstack 进程号 > deadlock_dump -
第三步:查看dump文件,筛选死锁信息
grep -A 20 "deadlock" deadlock_dump
核心结论:jstack会自动识别死锁,在dump文件中明确标注“Found one Java-level deadlock”,并列出死锁线程、持有锁和等待锁的信息,直接定位问题。
4. 死锁避免方案
-
方案1:按固定顺序获取锁(破坏循环等待);
-
方案2:使用
tryLock(long timeout, TimeUnit unit)(超时自动释放锁,破坏不可剥夺); -
方案3:一次性获取所有需要的锁(破坏持有并等待);
-
方案4:减少锁的持有时间,避免锁嵌套(降低死锁概率)。
五、线上实战:Linux 线程排查命令
线上排查死锁、线程阻塞、线程状态,核心使用 jstack 工具,抓取线程快照:
1. 导出线程 dump 文件
bash
运行
sudo -u admin jstack 进程号 > dump17
dump:抓取某一瞬间线程内存快照,用于分析线程状态。
2. 筛选线程状态
bash
运行
grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'
grep:筛选关键字;awk:统计输出信息。
3. 系统监控工具
vmstat:系统资源监控;Lmbench3:性能基准测试。
六、总结
Java 并发编程,上层是线程、锁、关键字的使用,底层是 CPU 缓存、操作系统调度、硬件总线的限制。
- 搞懂线程生命周期、上下文切换,理解多线程调度逻辑;
- 分清
sleep/wait/volatile/synchronized区别,避开锁使用误区; - 理解 CPU 缓存、内存屏障,明白并发安全问题的硬件根源;
- 掌握
jstack排查命令,具备线上多线程问题定位能力。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)