java多线程-死锁分析

死锁概念

所谓的死锁指的是多个线程之间因为竞争同一系统资源从而造成的一种僵局(互相等待)现象。此时若无外力作用,这些线程都将无法继续往下执行。

比如:线程A和线程B互相等待对方持有的锁从而导致程序无限死循环下去

死锁重现

实现死锁的步骤分三步走:

1、两个线程里面分别持有两个对象锁:lock1和lock2,这两个lock对象作为同步代码块的锁
2、在线程1的run()方法中使用同步块先获取lock1的对象锁,之后线程休眠,休眠时间不需要太长,50毫秒足够;然后再接着获取lock2的对象锁,这样做的目的主要是为了防止线程1启动之后一下子就连续获得了lock1和lock2两个对象的锁使得无法重现死锁
3、在线程2的run()方法中使用同步块先获取lock2的对象锁,之后线程休眠,然后再接着获取lock1的对象锁,当然,此时lock1的对象锁已经被线程1持有,线程2必然是要等到线程1释放lock1之后才能获取

代码实现

我们编写一个MyDeadLockTest.java

import java.util.concurrent.TimeUnit;

/**
 * 测试死锁:
 * 1、首先,leftLock线程代理类启动的时候,先获取left对象锁,之后休眠2秒
 * 2、接着,rightLock线程代理类启动的时候,先获取right对象锁,之后休眠2秒
 * 3、leftLock休眠结束后,需要先获取right对象锁才能继续执行,而此时right已被rightLock锁定
 * 4、rightLock休眠结束后,需要先获取left对象锁才能继续执行,而此时left已被leftLock锁定
 * 5、于是,leftLock和rightLock互相等待,都需要等待对方先释放锁从而获得锁资源继而才能继续往下执行,
 * 此时如果没有外力的作用是不可能做到的,因此导致了死锁的情况
 * Created by feizi on 2018/5/25.
 */
public class MyDeadLockTest {
    public static void main(String[] args) {
        MyDeadLock lock = new MyDeadLock();
        ProxyLeftLock leftLock = new ProxyLeftLock(lock);
        ProxyRightLock rightLock = new ProxyRightLock(lock);

        leftLock.start();
        rightLock.start();
    }
}

/**
 * 死锁例子
 */
class MyDeadLock {
    private final Object left = new Object();
    private final Object right = new Object();

    /**
     * 左边
     * @throws InterruptedException
     */
    public void left() throws InterruptedException {
        synchronized (left){
            TimeUnit.SECONDS.sleep(2);
            synchronized (right){
                System.out.println("左边...");
            }
        }
    }

    /**
     * 右边
     */
    public void right() throws InterruptedException {
        synchronized (right){
            TimeUnit.SECONDS.sleep(2);

            synchronized (left){
                System.out.println("右边...");
            }
        }
    }
}

/**
 * 多线程执行代理类-左边
 */
class ProxyLeftLock extends Thread{
    private MyDeadLock lock;

    public ProxyLeftLock(MyDeadLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            lock.left();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 多线程执行代理类-右边
 */
class ProxyRightLock extends Thread {
    private MyDeadLock lock;

    public ProxyRightLock(MyDeadLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            lock.right();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

之后,把我们编写的这个类丢到Linux环境下编译运行一下试试。

  1. 首先执行javac MyDeadLockTest.javajava文件编译成class文件

    1.jpg

  2. 然后执行java MyDeadLockTest命令并查看运行结果

    2.jpg

  3. 结果我们看到执行上述命令之后什么也没打印,因为此时已经形成了死锁,程序卡住了。接下来我们来验证一下,此时不要执行ctrl + c退出,我们另开一个窗口执行jps命令获取当前Java虚拟机进程的pid.

    3.jpg

  4. 执行jstack pid命令打印线程堆栈信息,不出意外的话我们将看到如下内容:

    4.jpg

  5. 在上述堆栈信息的最后,我们看到分析结果:发现了一个java级别的死锁,并且显示出了具体引起死锁的代码行数在MyDeadLockTest的第39行和第52行。

    5.jpg

6、回过头来我们看下具体的代码,找到MyDeadLockTest的第39行和第52行。

10.jpg

从上面打印的堆栈信息,其实我们就可以看出程序已经发生死锁了。

"Thread-1" prio=10 tid=0x00007fc4880a7800 nid=0x7cd7 waiting for monitor entry [0x00007fc47e762000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at MyDeadLock.right(MyDeadLockTest.java:52)
    - waiting to lock <0x00000007d704ca30> (a java.lang.Object)
    - locked <0x00000007d704ca40> (a java.lang.Object)
    at ProxyRightLock.run(MyDeadLockTest.java:91)

"Thread-0" prio=10 tid=0x00007fc4880a5800 nid=0x7cd6 waiting for monitor entry [0x00007fc47e863000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at MyDeadLock.left(MyDeadLockTest.java:39)
    - waiting to lock <0x00000007d704ca40> (a java.lang.Object)
    - locked <0x00000007d704ca30> (a java.lang.Object)
    at ProxyLeftLock.run(MyDeadLockTest.java:71)

死锁原因分析

首先,我们仅从代码的层面推测一下产生死锁的原因:

1、首先,leftLock线程代理类启动的时候,先获取left对象锁,之后休眠2秒
2、接着,rightLock线程代理类启动的时候,先获取right对象锁,之后休眠2秒
3、leftLock休眠结束后,需要先获取right对象锁才能继续执行,而此时right已被rightLock锁定
4、rightLock休眠结束后,需要先获取left对象锁才能继续执行,而此时left已被leftLock锁定
5、于是,leftLock和rightLock互相等待,都需要等待对方先释放锁从而获得锁资源继而才能继续往下执行,此时如果没有外力的作用是不可能做到的,因此导致了死锁的情况

接下来我们来分别介绍一下每一部分的意思,以上面的"Thread-1"为例:

1、"Thread-1"表示线程名称,
2、prio=10表示线程优先级,这里为10
3、tid=0x00007fc4880a7800表示线程ID
4、nid=0x7cd7表示线程对应的本地线程ID,Linux环境下可以使用top -Hp JVM进程Id来查看JVM进程下的本地线程(也被称作LWP)信息,注意这个本地线程是用十进制表示的,nid是用16进制表示的,转换一下就行了。
5、0x00007fc47e762000表示给线程分配的内存地址
6、java.lang.Thread.State: BLOCKED表示当前线程所处的状态

从上面打印的线程堆栈信息我们可以看出,Thread-1处于BLOCKED阻塞状态中,Thread-0也处于BLOCKED阻塞状态中,并且:

1、Thread-1锁住了<0x00000007d704ca40>,等待获取锁<0x00000007d704ca30>
2、Thread-0锁住了<0x00000007d704ca30>,等待获取锁<0x00000007d704ca40>

由于Thread-1Thread-0这两个线程同时都在等待获取对方持有的锁,所以就这么永久地等待下去了,从而造成了死锁。

最后,我们安利一款JAVA性能分析工具(Java Mission Control),简称:JMC。教程请参考https://www.cnblogs.com/duanxz/p/8533174.htmlhttps://www.cnblogs.com/aurain/p/6178671.html这两篇文章。这个JMC是jdk自带的一个性能分析工具,功能非常强大,主要是对JVM进程进行监控,包括占用的CPU资源、内存分配情况,GC回收情况,线程运行情况都可以进行分析。

我们在idea中启动时,先配置JVM参数开启JFR

-XX:+UnlockCommercialFeatures
-XX:+FlightRecorder

我们在idea中配置启动JVM参数信息


6.jpg

运行jdk安装目录下面的bin目录里面的jmc.exe程序


9.jpg

选择本地监控的java进程,可以查看具体的线程信息


7.jpg
8.jpg

从上面查看界面分析的结果,我们一样也看到了发生了死锁。

如何避免死锁

在有些情况下死锁是可以避免的,常用的避免死锁的技术有以下三种:

  1. 加锁顺序(线程按照一定的顺序加锁)
  2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
  3. 死锁检测

原文参考:

  1. https://www.cnblogs.com/xujingyang/p/6677160.html
  2. https://blog.csdn.net/ls5718/article/details/51896159
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容