死锁

 

/**
 * 一个简单的死锁类
 * main方法中启动两个线程,分别调用methodA和methodB方法
 * methodA方法首先获取到a对象的锁,睡眠1秒钟
 * 此时methodB方法执行获取到b对象的锁,睡眠1秒
 * 此时methodA需要去获取b对象的锁才能继续执行,但是b锁没有被释放无法获取到
 * 此时methodB需要去获取a对象的锁才能继续执行,但是a锁没有被释放无法获取到
 * 从而两者相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
 */

public class Main {
    private static String a = "1";
    private static String b = "2";
    public static void main(String[] args) {
        new ThreadA().start();
        new ThreadB().start();
    }
    static class ThreadA extends Thread {
        @Override
        public void run() {
            synchronized (a) {
                System.out.println("我是A方法中获得到了A锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println("我是A方法中获取到b锁");
                }
            }
        }
    }
    static class ThreadB extends Thread {
        @Override
        public void run() {
            synchronized (b) {
                System.out.println("我是B方法中获得到了b锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a) {
                    System.out.println("我是B方法中获取到a锁");
                }
            }
        }
    }
}

 产生死锁的四大必要条件

①资源互斥/资源不共享

互斥条件:一个资源每次只能被一个进程使用。

②占有和等待/请求并保持

(请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

③资源不可剥夺

 进程已获得的资源,在末使用完之前,不能强行剥夺。

④环路等待

若干进程之间形成一种头尾相接的循环等待资源关系。

 

避免死锁的方式

  1. 银行家算法

  1. 避免多次锁定。尽量避免同一个线程对多个 Lock 进行锁定。例如上面的死锁程序,主线程要对 A、B 两个对象的 Lock 进行锁定,副线程也要对 A、B 两个对象的 Lock 进行锁定,这就埋下了导致死锁的隐患。
  2. 具有相同的加锁顺序。(给锁添加顺序)如果多个线程需要对多个 Lock 进行锁定,则应该保证它们以相同的顺序请求加锁。比如上面的死锁程序,主线程先对 A 对象的 Lock 加锁,再对 B 对象的 Lock 加锁;而副线程则先对 B 对象的 Lock 加锁,再对 A 对象的 Lock 加锁。这种加锁顺序很容易形成嵌套锁定,进而导致死锁。如果让主线程、副线程按照相同的顺序加锁,就可以避免这个问题。
  3. 使用定时锁。程序在调用 acquire() 方法加锁时可指定 timeout 参数,该参数指定超过 timeout 秒后会自动释放对 Lock 的锁定,这样就可以解开死锁了。
  4. 死锁检测。死锁检测是一种依靠算法机制来实现的死锁预防机制,它主要是针对那些不可能实现按序加锁,也不能使用定时锁的场景的。

 

死锁的解除

1、资源剥夺法

挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源,而处于资源匮乏的状态。

2、撤销进程法

强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。

3、进程回退法

让一(多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息,设置还原点。

 

如何查看死锁

Java中当我们的开发涉及到多线程的时候,这个时候就很容易遇到死锁问题,刚开始遇到死锁问题的时候,我们很容易觉得莫名其妙,而且定位问题也很困难。

 

因为涉及到java多线程的时候,有的问题会特别复杂,而且就算我们知道问题出现是因为死锁了,我们也很难弄清楚为什么发生死锁,那么当我们遇到了死锁问题,我们应该如何来检测和查看死锁呢?

Java中jdk 给我们提供了很便利的工具,帮助我们定位和分析死锁问题:

Jconsole查看死锁

进入java安装的位置,输入Jconsole,然后弹出界面(或者进入安装目录/java/jdk1.70_80/bin/,点击Jconsole.exe):

Jstack查看死锁:

同样,也是进入jdk安装目录的bin下面,输入jps,先查看我们要检测死锁的进程:

然后可以看到进程Test的进程号:8384,然后执行:Jstack -l 8384

查看死锁信息: