众所周知死锁产生的4个条件是:
(1)互斥条件:即一个资源每次只能被一个进程使用;
(2)请求与保持条件:一个进程因为请求资源而处于阻塞时,已经获得的资源不会释放;
(3)不剥夺条件:进程已获得的资源,在没有使用完之前,不能被强行剥夺;
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;
先看个死锁的简单案例:
public class Deadlock1 {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
//启动一个线程,重写run方法,取名:“线程1”
new Thread(()->{
synchronized (o1){
try {
//睡眠1秒,模拟执行业务代码
System.out.println("线程1获取到了第一把锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println("线程1获取到了第二把锁");
}
}
},"线程1").start();
//启动一个线程,重写run方法,取名:“线程2”
new Thread(()->{
synchronized (o2){
try {
//睡眠1秒,模拟执行业务代码
System.out.println("线程2获取到了第一把锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
System.out.println("线程2获取到了第二把锁");
}
}
},"线程2").start();
}
}
可以发现,JVM并没有停止,线程1拿到o1的锁,然后想去拿o2的锁。线程2拿到了o2的锁,想去拿o1的锁,此时就产生了死锁。同时我们可以在控制台输入:jps,查看当前jvm进程:
找到当前类的进程Id:21996 Deadlock1,继续控制台输入:jstack 21996,查看进程下的线程
可以发现这里提示我们有一个死锁。
所以要解决死锁,只需要解决任意一个问题即可,但是第一个互斥条件是互斥锁的基本要求,属于不可破坏类型。再看第二个条件,
(2)请求与保持条件:一个进程因为请求资源而处于阻塞时,已经获得的资源不会释放;
所以我们只需要让线程一次性把所有锁获取完毕即可解决这个问题,如图:
上代码:
协调者
/**
* 协调者,需要保证协调者只有一个,可以用单例实现。
*/
public class Coordinator {
private static List<Object> list = new LinkedList<>();
//申请锁资源
public static synchronized Boolean apply(Object o1,Object o2){
if(list.contains(o1) || list.contains(o2)){
return false;
}else {
list.add(o1);
list.add(o2);
//申请所有资源成功。
return true;
}
}
//释放锁资源
public static synchronized void freed(Object o1,Object o2){
list.remove(o1);
list.remove(o2);
}
测试:
public class Deadlock2 {
public static void main(String[] args) throws InterruptedException {
//两个锁资源
Object o1 = new Object();
Object o2 = new Object();
//启动一个线程,重写run方法,取名:“线程1”
new Thread(()->{
while (true) {
//如果申请到了所有锁资源就可以执行
if (Coordinator.apply(o1, o2)) {
synchronized (o1) {
try {
//睡眠1秒,模拟执行业务代码
System.out.println("线程1获取到了第一把锁");
Thread.sleep(1000);
synchronized (o2) {
System.out.println("线程1获取到了第二把锁");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁,写在final中,保证能释放到锁
Coordinator.freed(o1, o2);
}
//结束循环
break;
}
} else {
//如果没用申请到锁,睡眠20毫秒,继续申请
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"线程1").start();
//启动一个线程,重写run方法,取名:“线程2”
new Thread(()->{
while (true) {
//如果申请到了所有锁资源就可以执行
if (Coordinator.apply(o1, o2)) {
synchronized (o1) {
try {
//睡眠1秒,模拟执行业务代码
System.out.println("线程2获取到了第一把锁");
Thread.sleep(1000);
synchronized (o2) {
System.out.println("线程2获取到了第二把锁");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁,写在final中,保证能释放到锁
Coordinator.freed(o1, o2);
}
//结束循环
break;
}
} else {
//如果没用申请到锁,睡眠20毫秒,继续申请
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"线程2").start();
}
}
看结果:
其实这里也有些问题,就是如果线程获取不到锁,会一直申请,那么可以根据业务场景来设置一些条件,即使多次获取不到锁,也可以让出线程的执行权。