什么是线程死锁
定义
死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
举例
假设有两个孩子分别为孩子A和孩子B,相当于两个线程,还有两个玩具,一个是苹果,一个是香蕉,一开始孩子A拿的是苹果,孩子B拿的是香蕉,之后过了一段时间,孩子A想要玩香蕉,孩子B想要玩苹果,但是他们都不愿意放下手中的玩具,结果就会导致双方都想要对方的玩具,但是又不放弃现在持有的玩具,就只能导致两方都在等待了,也就是我们所说的死锁了
package deadLock;
/**
* @Author: WalkerShen
* @DATE: 2022/3/15
* @Description: 死锁
**/
public class DeadLockDemo {
/**
* 模拟场景:
* 线程0 获取资源1 休眠 获取资源2
* 线程1 获取资源2 休眠 获取资源1
*/
//定义两个资源
//资源1
private final static String apple="a";
//资源2
private final static String banana="b";
public static void main(String[] args) {
//定义线程A
new Thread(()->{
//使用synchronized进行加锁资源1
synchronized (apple){
System.out.println(Thread.currentThread().getName()+"获取苹果");
//这里休眠1秒,确保线程B对资源2进行持有
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"等待获取香蕉");
//此时,未曾释放资源1,但是想获取资源2的锁,但是资源2的锁已经被线程B持有,就会导致两方互相等待对方资源,导致死锁
synchronized (banana){
System.out.println(Thread.currentThread().getName()+"获取香蕉");
}
}
},"孩子A").start();
//定义线程B
new Thread(()-> {
//使用synchronized进行加锁资源2
synchronized (banana) {
System.out.println(Thread.currentThread().getName() + "获取香蕉");
//这里休眠1秒,确保线程A对资源1进行持有
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//加锁资源1
System.out.println(Thread.currentThread().getName()+"等待获取苹果");
synchronized (apple) {
System.out.println(Thread.currentThread().getName() + "获取苹果");
}
}
},"孩子B").start();
}
}
执行结果:
孩子A获取苹果
孩子B获取香蕉
孩子B等待获取苹果
孩子A等待获取香蕉
形成死锁的四个必要条件是什么
互斥条件:
- 线程(进程)对于所分配到的资源具有排它性,
- 即一个资源只能被一个线程(进程)占用,直到被该线程(进程)释放
请求与保持条件:
- 一个线程(进程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
不剥夺条件:
- 线程(进程)已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
循环等待条件:
- 当发生死锁时,所等待的线程(进程)必定会形成一个环路(类似于死循环),造成永久阻塞
如何避免线程死锁 3`
我们只要破坏产生死锁的四个条件中的其中一个就可以了。
破坏互斥条件
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)
破坏请求与保持条件
一次性申请所有的资源。
package deadLock;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: WalkerShen
* @DATE: 2022/3/15
* @Description: 破坏请求和保持
**/
public class BreakRequestAndKeep {
/**
* 孩子A和孩子B分别一次性持有所有的资源
* 这里是将资源都放在了一个数组里面,然后进行持有
*/
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
new Thread(()->{
synchronized (list){
System.out.println(Thread.currentThread().getName()+"持有苹果和香蕉");
}
},"孩子A").start();
new Thread(()->{
synchronized (list){
System.out.println(Thread.currentThread().getName()+"持有苹果和香蕉");
}
},"孩子B").start();
}
}
破坏不剥夺条件
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
案例
这里先双方都先获取苹果,之后再获取香蕉
假设孩子A先获取到苹果,那么孩子B就会等待
之后孩子A会在持有苹果的过程中再去获取香蕉,获取完香蕉之后,会将两个玩具都放回原处,之后孩子B就可以去拿苹果和香蕉了
package deadLock;
/**
* @Author: WalkerShen
* @DATE: 2022/3/15
* @Description: 破坏循环等待
**/
public class BreakCircularWait {
//定义两个资源
//资源1
private final static String apple="apple";
//资源2
private final static String banana="banana";
public static void main(String[] args) {
new Thread(()->{
synchronized (apple){
System.out.println(Thread.currentThread().getName()+"获取"+apple);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"等待"+banana);
synchronized (banana){
System.out.println(Thread.currentThread().getName()+"获取"+banana);
}
}
},"孩子A").start();
new Thread(()-> {
synchronized (apple) {
System.out.println(Thread.currentThread().getName() +"获取"+ banana);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"等待"+banana);
synchronized (banana) {
System.out.println(Thread.currentThread().getName() +"获取"+ banana);
}
}
},"孩子B").start();
}
}