声明:本文是自己自学慕课网悟空老师的《Java并发核心知识体系精讲》的死锁部分后整理而成课程笔记。
课程链接如下:https://coding.imooc.com/class/362.html
如有侵权,请私信我并第一时间删除本文。
1. 死锁是什么?有什么危害
1.1什么是死锁
-
发生在并发中
-
互不相让:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法继续前进, 导致程序陷入无尽的阻塞,这就是死锁。
-
一图胜千言
-
多个线程造成死锁的情况
如果多个线程之间的依赖关系是环形,存在环路的锁的依赖关系,那么也可能会发生死锁
1.2 死锁的影响
死锁的影响在不同系统中是不一样的,这取决于系统对死锁的处理能力
◆数据库中:检测并放弃事务
◆JVM中:无法自动处理
2. 发生死锁例子
2.1 最简单的死锁
/**
* 描述: 必定发生死锁的情况
*/
public class MustDeadLock implements Runnable {
int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) {
MustDeadLock r1 = new MustDeadLock();
MustDeadLock r2 = new MustDeadLock();
r1.flag = 1;
r2.flag = 0;
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("线程1成功拿到两把锁");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("线程2成功拿到两把锁");
}
}
}
}
}
分析:
- 当类的对象flag=1时(T1) ,先锁定O1,睡眠500毫秒, 然后锁定O2 ;
- 而T1在睡眠的时候另一个flag=0的对象(T2)线程启动,先锁定O2,睡眠500毫秒,等待T1释放01 ;
- T1睡眠结束后需要锁定O2才能继续执行,而此时O2已被T2锁定;
- T2睡眠结束后需要锁定O1才能继续执行,而此时O1已被T1锁定;
- T1、T2相互等待,都需要对方锁定的资源才能继续执行,从而死锁。
注意看退出信号: Process finished with exit code 130(interrupted by signal 2: SIGINT) ,是不正常退出的信号,对比正常结束的程序的结束信号是0。
2.2 实际生产中的例子:转账
-
需要两把锁
-
获取两把锁成功,且余额大于0 ,则扣除转出人,增加收款人的余额,是原子操作
-
顺序相反导致死锁
/** * 描述: 转账时候遇到死锁,一旦打开注释,便会发生死锁 */ public class TransferMoney implements Runnable { int flag = 1; static Account a = new Account(500); static Account b = new Account(500); static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { TransferMoney r1 = new TransferMoney(); TransferMoney r2 = new TransferMoney(); r1.flag = 1; r2.flag = 0; Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("a的余额" + a.balance); System.out.println("b的余额" + b.balance); } @Override public void run() { if (flag == 1) { transferMoney(a, b, 200); } if (flag == 0) { transferMoney(b, a, 200); } } public static void transferMoney(Account from, Account to, int amount) { synchronized (from){ /**加上sleep就造成死锁了 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } */ synchronized (to){ if (from.balance - amount < 0){ System.out.println("余额不足,转账失败。"); } from.balance -= amount; to.balance += amount; System.out.println("成功转账" + amount + "元"); } } } static class Account { public Account(int balance) { this.balance = balance; } int balance; } }
未加注释代码部分,运行结果如下
加上注释后运行结果如下,由于请求锁的顺序相反,造成相互等待,互不退让,造成死锁。
2.3 模拟多人随机转账
- 5万人很多,但是依然会发生死锁,墨菲定律
- 复习:发生死锁几率不高但危害大
import java.util.Random;
/**
* 描述: 多人同时转账,依然很危险
*/
public class MultiTransferMoney {
private static final int NUM_ACCOUNTS = 500;
private static final int NUM_MONEY = 1000;
private static final int NUM_ITERATIONS = 1000000;
private static final int NUM_THREADS = 20;