今天我们来了解一下线程死锁,死锁很好理解,从字面上来看就是锁死了,解不开,在大街上看到一对卧龙凤雏的情侣,怎么说,你们给我锁死,不要分开去霍霍别人
之前我们不是说过,解决线程安全的方法就是给线程上锁,java进阶—线程安全问题,但上锁也会有死锁的情况
那么,死锁是什么?先举个通俗点的例子 小明跟小红分别同时参加两个会议,这时候办公室刚好只有一台笔记本(在小红手上),一台投影仪(在小明手上),这是两个都想要对方的东西,两人互不相让,开始争执,这样都开不成会议,就形成了死锁
把小明跟小红换成两个线程,所以,一句话,死锁就是两个或两个以上的线程争夺彼此的锁,造成阻塞
从这里我们也可以看到死锁产生的条件
-
首先,死锁产生需要两个或者两个以上线程 (例子中的小明跟小红)
-
两个或者两个以上的锁 (例子中的 笔记本跟投影仪)
-
两个或两个以上线程持有不同锁(例子中小明有投影仪,小红有笔记本)
-
持有不同锁线程争夺对方的锁 (例子中小明跟小红抢对方的东西)
用代码来解释上述例子
我们按照死锁的四个条件一步一步来
1. 首先有两个线程
一个小明类,一个小红类
2. 两个锁
小明类 里面 有 投影仪锁 , 小红类里面有电脑锁
3. 两个线程持有不同的锁
小明重写run 方法 调用 投影仪 锁
小红重写run 方法 调用 笔记本 锁
4. 持有不同锁调用对方的锁
小明在projector 里面去调用 小红的computer
小红在computer里面去调用 小红的projector
public class XiaoMing implements Runnable{
@Override
public void run() {
//小明持有投影仪这个锁
projector();
}
/**
* 投影仪这个锁 static 一个资源
*/
public static synchronized void projector() {
try {
//线程休眠2秒,目的为了执行慢一点,更方便看出效果
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("小明的投影仪");
// 获取小红的锁
XiaoHong.computer();
}
}
public class XiaoHong implements Runnable{
@Override
public void run() {
//小红持有笔记本这个锁
computer();
}
/**
* 笔记本这个锁 static 一个资源
*/
public static synchronized void computer() {
try {
//线程休眠2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("小红的笔记本");
//获取小明的锁
XiaoMing.projector();
}
}
我们去启动这两个线程
public static void main(String[] args) {
//创建小红线程
XiaoHong xiaoHong =new XiaoHong();
Thread thread =new Thread(xiaoHong);
//创建小明线程
XiaoMing xiaoMing = new XiaoMing();
Thread thread1 =new Thread(xiaoMing);
//启动
thread.start();
thread1.start();
}
执行结果:
程序在打印两个结果后,两个程序在相互争夺资源,程序停止需要借助外力
解决死锁也就很简单了,我们不去拿对方的锁就好了
这一步,我们不去调用对方的锁,
看看效果:
可以看到程序正常终止了
要是不太明白,我们再来看一个更直观的代码:(注意看代码中的注释)
还是有这么两个资源,电脑跟投影仪
public class Person implements Runnable {
/**
* 只有一份资源,电脑 跟 投影仪
*/
static final Computer COMPUTER = new Computer();
static final Projector PROJECTOR = new Projector();
/**
* 类型 0 代表 小红 1代表小明
*/
int type;
/**
* 人名
*/
String name;
public Person(int type, String name) {
this.type = type;
this.name = name;
}
@Override
public void run() {
//开始抢占资源
try {
getSource();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void getSource() throws InterruptedException {
//让小明先拿投影仪
if (type==1) {
synchronized (PROJECTOR) {
System.out.println(this.name+"获得投影仪");
Thread.sleep(2000);
//小明拿到投影仪,又想去拿电脑
synchronized (COMPUTER) {
System.out.println(this.name+"获得电脑");
}
}
}else {
//小红拿到电脑,又想去拿投影仪
synchronized (COMPUTER) {
System.out.println(this.name+"获得电脑");
Thread.sleep(1000);
synchronized (PROJECTOR) {
System.out.println(this.name+"获得投影仪");
}
}
}
}
}
启动两个线程
public static void main(String[] args) {
Person person =new Person(1,"小明");
Person person2 =new Person(0,"小红");
Thread thread =new Thread(person);
Thread thread1 =new Thread(person2);
thread.start();
thread1.start();
}
执行结果:
程序进入了死锁
怎么解决?一样的,获取到锁的同时不去调用对方的锁,我们这两段代码放到外边来
变成下面这样
package com.xrp.flinkDemo.demo;
public class Person implements Runnable {
/**
* 只有一份资源,电脑 跟 投影仪
*/
static final Computer COMPUTER = new Computer();
static final Projector PROJECTOR = new Projector();
/**
* 类型 0 代表 小红 1代表小明
*/
int type;
/**
* 人名
*/
String name;
public Person(int type, String name) {
this.type = type;
this.name = name;
}
@Override
public void run() {
//开始抢占资源
try {
getSource();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void getSource() throws InterruptedException {
//让小明先拿投影仪
if (type==1) {
synchronized (PROJECTOR) {
System.out.println(this.name+"获得投影仪");
Thread.sleep(2000);
}
synchronized (COMPUTER) {
System.out.println(this.name+"获得电脑");
}
}else {
//小红拿到电脑,又想去拿投影仪
synchronized (COMPUTER) {
System.out.println(this.name+"获得电脑");
Thread.sleep(1000);
}
synchronized (PROJECTOR) {
System.out.println(this.name+"获得投影仪");
}
}
}
}
再执行看看:
可以发现解决了死锁问题,并且都获得了想要的资源
好了,以上便是死锁原因以及避免死锁方法了,主要就是不要在持有一个锁里面再去获取别的锁
【完】