mirror of
https://github.com/CyC2018/Interview-Notebook.git
synced 2026-10-31 02:53:42 +00:00
Update 计算机操作系统 - 死锁.md
补充各种预防方法的代价、死锁避免的前提、死锁线程的状态,附录 A4。
This commit is contained in:
+52
-5
@@ -24,6 +24,7 @@
|
||||
* [A1 Java 线程死锁](#a1-java-线程死锁)
|
||||
* [A2 MySQL 死锁](#a2-mysql-死锁)
|
||||
* [A3 先查后插的间隙锁死锁](#a3-先查后插的间隙锁死锁)
|
||||
* [A4 死锁线程的状态与 CPU 时间](#a4-死锁线程的状态与-cpu-时间)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
@@ -103,7 +104,7 @@
|
||||
|
||||
## 死锁预防
|
||||
|
||||
在程序运行之前预防发生死锁。
|
||||
在程序运行之前预防发生死锁:四个必要条件缺一不可,设计时破坏其中任意一个,死锁就不可能发生。各种方法的代价不同,实际开发中最常用的是破坏环路等待。
|
||||
|
||||
### 1. 破坏互斥条件
|
||||
|
||||
@@ -111,7 +112,7 @@
|
||||
|
||||
### 2. 破坏占有和等待条件
|
||||
|
||||
一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。
|
||||
一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。代价是资源被提前占用、利用率低,而且很多程序事先并不知道需要哪些资源。
|
||||
|
||||
### 3. 破坏不可抢占条件
|
||||
|
||||
@@ -121,11 +122,11 @@
|
||||
|
||||
### 4. 破坏环路等待
|
||||
|
||||
给资源统一编号,进程只能按编号顺序来请求资源。
|
||||
给资源统一编号,进程只能按编号顺序来请求资源。这样所有进程都是从小编号往大编号等待,不可能形成环。它的代价最小,只要求每个加锁的地方遵守同一个顺序,例子见 [Java 线程死锁](#1-java-线程死锁) 和 [MySQL 死锁](#2-mysql-死锁)。
|
||||
|
||||
## 死锁避免
|
||||
|
||||
在程序运行时避免发生死锁。
|
||||
在程序运行时避免发生死锁。死锁避免不限制资源的请求方式,而是在每次分配前判断分配之后系统是否仍处于安全状态,不安全就让请求者等待。它要求事先知道每个进程最多需要多少资源,通用的操作系统和应用程序很难满足,因此主要见于教材和特定的资源分配系统。
|
||||
|
||||
### 1. 安全状态
|
||||
|
||||
@@ -203,7 +204,9 @@ synchronized (first) {
|
||||
}
|
||||
```
|
||||
|
||||
两个线程各反向转账 10 万次,原来的写法每次运行都会卡住,按 id 顺序加锁后都能完成,两个账户的余额仍为 1000(JDK 21 实测,见[附录 A1](#a1-java-线程死锁))。使用 ReentrantLock 时还可以用 tryLock 设置超时,拿不到锁就释放已持有的锁再重试,破坏的是占有和等待条件。
|
||||
两个线程各反向转账 10 万次,原来的写法每次运行都会卡住,按 id 顺序加锁后都能完成,两个账户的余额仍为 1000(JDK 21 实测,见[附录 A1](#a1-java-线程死锁))。使用 ReentrantLock 时还可以用 tryLock 设置超时,拿不到锁就释放已持有的锁再重试,破坏的是占有和等待条件。例如获取第二把锁等待 10 ms 仍失败时释放第一把锁,随机等待 1~4 ms 后重试,同样的两个线程在 30~60 ms 内完成(见[附录 A4](#a4-死锁线程的状态与-cpu-时间))。等待时间要随机,否则两个线程可能总是同时释放、同时重试,谁都拿不到两把锁,这称为活锁。
|
||||
|
||||
需要注意的是,死锁的线程处于阻塞状态,并不消耗 CPU。上例卡住后,两个线程的状态都是 BLOCKED,之后 3 秒内各自消耗的 CPU 时间都不到 0.1 ms(JDK 21 实测,见[附录 A4](#a4-死锁线程的状态与-cpu-时间))。因此 CPU 占用很高时应先按 [CPU 占用过高](Java%20虚拟机.md#cpu-占用过高)排查死循环、频繁 GC 等问题;CPU 很低、请求却全部卡住,线程都停在加锁的地方,才更像死锁,用 jstack 或 jcmd 的 Thread.print 命令打印线程栈确认。程序中也可以定时调用 ThreadMXBean 的 findDeadlockedThreads() 检测死锁并告警。
|
||||
|
||||
### 2. MySQL 死锁
|
||||
|
||||
@@ -333,9 +336,53 @@ ERROR 1062 (23000) at line 1: Duplicate entry '1024' for key 'payment.PRIMARY'
|
||||
|
||||
执行 SET GLOBAL innodb_print_all_deadlocks = ON 后重跑可重复读的例子,错误日志中同样记录了两个事务持有的间隙锁和等待的插入意向锁(lock_mode X locks gap before rec insert intention waiting)。
|
||||
|
||||
### A4 死锁线程的状态与 CPU 时间
|
||||
|
||||
JDK 21.0.13,macOS(Apple M1 Pro)。用正文中的 transfer 让线程 T1、T2 反向转账直到卡住,等待 5 秒后,用 ThreadMXBean 记录两个线程此后 3 秒内消耗的 CPU 时间,再调用 findDeadlockedThreads():
|
||||
|
||||
```java
|
||||
ThreadMXBean mx = ManagementFactory.getThreadMXBean();
|
||||
long c1 = mx.getThreadCpuTime(t1.threadId()), c2 = mx.getThreadCpuTime(t2.threadId());
|
||||
Thread.sleep(3000);
|
||||
long d1 = mx.getThreadCpuTime(t1.threadId()) - c1, d2 = mx.getThreadCpuTime(t2.threadId()) - c2;
|
||||
for (ThreadInfo ti : mx.getThreadInfo(mx.findDeadlockedThreads())) { ... }
|
||||
```
|
||||
|
||||
```text
|
||||
T1 状态 BLOCKED,T2 状态 BLOCKED
|
||||
之后 3 秒内 T1、T2 消耗的 CPU 时间:62000 ns、35000 ns
|
||||
findDeadlockedThreads:T1 等待 DeadlockDiag$Account@7291c18f,持有者 T2
|
||||
findDeadlockedThreads:T2 等待 DeadlockDiag$Account@1b2c6ec2,持有者 T1
|
||||
```
|
||||
|
||||
改用 ReentrantLock.tryLock 的版本(获取第二把锁最多等待 10 ms,失败则释放第一把锁,随机等待 1~4 ms 后重试),各转账 10 万次,运行 3 次:
|
||||
|
||||
```java
|
||||
static void transferTry(Account from, Account to, int amount) throws InterruptedException {
|
||||
while (true) {
|
||||
if (from.lock.tryLock(10, TimeUnit.MILLISECONDS)) {
|
||||
try {
|
||||
if (to.lock.tryLock(10, TimeUnit.MILLISECONDS)) {
|
||||
try { from.balance -= amount; to.balance += amount; return; }
|
||||
finally { to.lock.unlock(); }
|
||||
}
|
||||
} finally { from.lock.unlock(); }
|
||||
}
|
||||
Thread.sleep(ThreadLocalRandom.current().nextInt(1, 5));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```text
|
||||
tryLock 版本:完成,余额 1000 1000,用时 59 ms
|
||||
tryLock 版本:完成,余额 1000 1000,用时 29 ms
|
||||
tryLock 版本:完成,余额 1000 1000,用时 43 ms
|
||||
```
|
||||
|
||||
## 参考资料
|
||||
|
||||
- Tanenbaum A S, Bos H. Modern Operating Systems [M]. Pearson, 2014.
|
||||
- [Deadlocks in InnoDB | MySQL 8.0 Reference Manual](https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html)
|
||||
- [InnoDB Locking | MySQL 8.0 Reference Manual](https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html)
|
||||
- [The data_locks Table | MySQL 8.0 Reference Manual](https://dev.mysql.com/doc/refman/8.0/en/performance-schema-data-locks-table.html)
|
||||
- [ThreadMXBean (Java SE 21 & JDK 21)](https://docs.oracle.com/en/java/javase/21/docs/api/java.management/java/lang/management/ThreadMXBean.html)
|
||||
|
||||
Reference in New Issue
Block a user