死锁的概念:多个线程互相持有对方所需要的资源,导致这些线程处于等待状态。
死锁产生的必要条件:
- 互斥:一个资源只允许一个线程访问(厕所一个坑只能一个人上厕所)
- 占有且等待:一个线程占有资源,同时还有其他线程未得到满足,正在等待其他线程释放该资源(厕所坑里进了一个人,外面还有一个人等着里面的人厕所用完)
- 不可抢占:其他线程已经占有了某项资源,不能因为你需要就抢过来(别人已经进坑里上厕所了,不能因为你也要上,直接把那人揪出来,不合适吧,哈哈)
- 循环等待:发生死锁时,所等待的线程必定会形成一个环路,造成永久阻塞
这么通俗易懂的理解一下死锁吧,比如A和B两个人,一个厕所坑位,一卷卫生纸,A进厕所蹲坑去了,即A占有了坑,但是A没拿卫生纸,B拿了卫生纸后,B也想上厕所了,发现坑位有人了,那在门外等候,A占有了坑位,B占有了纸,A想得到纸,但是发现被B占了,都他妈没纸,怎么出来,B想上厕所,发现厕所被A占了,那他妈的怎么办,最后形成了僵持局面,A拿不到纸就占着坑不出去,B进不去坑我就拿着纸,然后两个就这样一直僵持着,形成了死锁。来一张撇脚的图片吧,电脑自带画图刚创作的,哈哈哈:
造成死锁的原因通俗的概括成:
- 当前线程拥有其他线程需要的资源
- 当前线程等待其他线程已拥有的资源
- 都不放弃自己拥有的资源
死锁造成的影响很少会显现出来,如果一个类可能发生死锁,那么一般是在高并发,很糟糕的时候发生死锁。
手写一个简单的死锁,下面这段代码加了延时,只要运行就会产生死锁:
public class DeadLock {
private final Object left = new Object();
private final Object right = new Object();
public void leftToRight() {
synchronized(left) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (right) {
System.out.println("得到right");
}
}
}
public void rightToLeft() {
synchronized(right) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(left) {
System.out.println("得到left");
}
}
}
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
deadLock.leftToRight();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
deadLock.rightToLeft();
}
}
}).start();
}
}
死锁预防:
互斥条件是必须的,不能被改变,还应该加以保证
1、破坏“占有且等待”:
- 所有线程在开始运行之前,必须一次性的申请在整个运行过程中所需要的全部资源
- 允许线程只获取运行初期需要的资源,在运行过程中逐步的释放掉使用完毕的资源
2、破坏“不可抢占条件”:
- 当一个已经持有了一些资源的线程在提出新的资源请求没有得到满足时,它必须释放掉已经保持的所有资源
3、破坏“循环等待”:
- 定义资源类型的线性顺序来预防
避免死锁的方法:
- 固定加锁顺序
- 开放调用
- 使用定时锁
- 死锁检测
总结
简单的回顾一下死锁的概念以及预防死锁等基础性的问题。