产生背景
简单的说:线程1 想要去拿一个由 线程2 持有的锁,由于synchronized 的锁是互斥锁,某一时刻只能被一个线程所持有,所以线程1 就拿不到锁。
死锁原因
是指两个
或两个以上
的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
原因如下:
- 因为系统资源不足。
- 进程运行推进的顺序不合适,这种产生的最多。
- 资源分配不当。
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,而该资源又被其他线程锁定,从而导致每一个线程都得等其它线程释放其锁定的资源,造成了所有线程都无法正常结束。
因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
产生背景: 在多线程环境下,争抢同是争抢对方资源(锁)就会产生该问题,即产生死锁。
java 死锁产生的四个必要条件
- 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
- 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
举列说明场景
代码语言:javascript复制/**
* 死锁
*/
public class SyncDeadLock {
private static Object locka = new Object();
private static Object lockb = new Object();
public static void main(String[] args) {
new SyncDeadLock().deadLock();
}
private void deadLock() {
Thread thread1 = new Thread(() -> {
synchronized (locka) {
try {
System.out.println(Thread.currentThread().getName() " 拿到 lock-A!");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() " 睡眠 500ms 后续继执行...!");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() " 偿试获取 lock-B!");
synchronized (lockb) {
System.out.println(Thread.currentThread().getName() " 已获得 lock-B!");
}
}
}, "thread1");
Thread thread2 = new Thread(() -> {
synchronized (lockb) {
try {
System.out.println(Thread.currentThread().getName() " 拿到 lock-B!");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() " 睡眠 500ms 后续继执行...!");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() " 偿试获取 lock-A!");
synchronized (locka) {
System.out.println(Thread.currentThread().getName() " 已获得 lock-A!");
}
}
}, "thread2");
thread1.start();
thread2.start();
}
}
结果
代码语言:javascript复制thread1 拿到 lock-A!
thread2 拿到 lock-B!
thread1 睡眠 500ms 后续继执行...!
thread1 偿试获取 lock-B!
thread2 睡眠 500ms 后续继执行...!
thread2 偿试获取 lock-A!
帮助理解
地上放着两个桶泡面,一个老坛酸菜,一个小鸡炖磨茹。两个人一个产品,一个测试,同时出发去抢老坛酸菜和小鸡炖磨茹,产品老坛酸菜,测试拿到小鸡炖磨茹,同一时刻,产品伸要去拽测试怀里的小鸡炖磨茹,测试伸手去拽产品的老坛酸菜,两个僵持不下,就死在那了,叫死锁。如果没有开发将他们各打一顿解救出来,它们奖无法推进下去。
死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
死锁预防
如果只使用一个锁就不会有死锁的问题,不过复杂场景下不太理实。
以确定的顺序获得锁
代码语言:javascript复制线程A ---> 锁定 A ----> 偿试锁定 B
线程B ---> 锁定 A ----> 偿试锁定 B
这样就不会发生死锁
超时放弃
Lock 接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException
方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。
信号量控制
代码语言:javascript复制import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 信号量控制,解决死锁
*/
public class UnLockTest {
public static final Semaphore a1 = new Semaphore(1);
public static final Semaphore a2 = new Semaphore(1);
public static void main(String[] args) {
LockAa la = new LockAa();
new Thread(la).start();
LockBb lb = new LockBb();
new Thread(lb).start();
}
}
class LockAa implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() " LockA 开始执行");
while (true) {
if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() " LockA 锁住 obj1");
if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() " LockA 锁住 obj2");
Thread.sleep(60 * 1000); // do something
}else{
System.out.println(new Date().toString() "LockA 锁 obj2 失败");
}
}else{
System.out.println(new Date().toString() "LockA 锁 obj1 失败");
}
UnLockTest.a1.release(); // 释放
UnLockTest.a2.release();
Thread.sleep(1000); // 马上进行尝试,现实情况下do something是不确定的
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockBb implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() " LockB 开始执行");
while (true) {
if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() " LockB 锁住 obj2");
if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() " LockB 锁住 obj1");
Thread.sleep(60 * 1000); // do something
}else{
System.out.println(new Date().toString() "LockB 锁 obj1 失败");
}
}else{
System.out.println(new Date().toString() "LockB 锁 obj2 失败");
}
UnLockTest.a1.release(); // 释放
UnLockTest.a2.release();
Thread.sleep(10 * 1000); // 这里只是为了演示,所以tryAcquire只用1秒,而且B要给A让出能执行的时间,否则两个永远是死锁
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码语言:javascript复制Sun Oct 10 12:15:25 CST 2018 LockA 锁住 obj1
Sun Oct 10 12:15:25 CST 2018 LockA 锁住 obj2
Sun Oct 10 12:15:43 CST 2018 LockB 锁住 obj2
Sun Oct 10 12:15:43 CST 2018 LockB 锁住 obj1
Sun Oct 10 12:16:26 CST 2018 LockA 锁住 obj1
Sun Oct 10 12:16:26 CST 2018 LockA 锁住 obj2
Sun Oct 10 12:16:53 CST 2018 LockB 锁住 obj2
Sun Oct 10 12:16:53 CST 2018 LockB 锁住 obj1
Sun Oct 10 12:17:27 CST 2018 LockA 锁住 obj1
Sun Oct 10 12:17:27 CST 2018 LockA 锁住 obj2
Sun Oct 10 12:18:03 CST 2018 LockB 锁住 obj2
Sun Oct 10 12:18:03 CST 2018 LockB 锁住 obj1
Sun Oct 10 12:18:28 CST 2018 LockA 锁住 obj1
Sun Oct 10 12:18:28 CST 2018 LockA 锁住 obj2
Sun Oct 10 12:19:13 CST 2018 LockB 锁住 obj2
Sun Oct 10 12:19:13 CST 2018 LockB 锁住 obj1