【评论抽掘金周边】| 谈谈java中的死锁

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

抽奖说明在文末!

什么是死锁

在使用多线程以及多进程时,两个或两个以上的运算单元(进程、线程或协程),各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,就称为死锁。

下面看个简单的例子:

public class DeadLockTest {
    public static void main(String[] args) {
        Object lock1 = new Object(); // 锁1
        Object lock2 = new Object(); // 锁2

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 先获取锁1
                synchronized (lock1) {
                    System.out.println("Thread 1:获取到锁1!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //获取锁2
                    System.out.println("Thread 1:等待获取2...");
                    synchronized (lock2) {
                        System.out.println("Thread 1:获取到锁2!");
                    }
                }
            }
        });
        t1.start(); 

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 先获取锁2
                synchronized (lock2) {
                    System.out.println("Thread 2:获取到锁2!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 获取锁1
                    System.out.println("Thread 2:等待获取1...");
                    synchronized (lock1) {
                        System.out.println("Thread 2:获取到锁1!");
                    }
                }
            }
        });
        t2.start(); 
    }
}
复制代码

死锁产生原因

死锁只有同时满足以下四个条件才会发生:

互斥条件:线程对所分配到的资源具有排它性,在某个时间内锁资源只能被一个线程占用。如下图:

死锁1.PNG

线程1已经持有的资源,不能再同时被线程2持有,如果线程2请求获取线程1已经占用的资源,那线程2只能等待,直到线程1释放资源。如下图:

持有并请求条件: 持有.PNG

持有并请求条件是指,当线程1已经持有了资源1,又想申请资源2,而资源2已经被线程3持有了,所以线程1就会处于等待状态,但是线程1在等待资源2的同时并不会释放已经持有的资源1。

不可剥夺条件:当线程已经持有了资源 ,在未使用完之前,不能被剥夺。如下图: 不可.PNG

线程2如果也想使用此资源,只能等待线程1使用完并释放后才能获取。

环路等待条件:在死锁发生的时候,两个线程获取资源的顺序构成了环形链,如下图:

环形.PNG 线程1已经持有资源2,想获取资源1, 线程2已经获取了资源1,想请求资源2,这就形成资源请求等待的环形图。

死锁的排查

1.jstack

用法如下:

/opt/java8/bin/jstack

Usage:
    jstack [-l] <pid>
        (to connect to running process) 连接活动线程
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process) 连接阻塞线程
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file) 连接dump的文件
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server) 连接远程服务器

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message
复制代码

死锁日志如下:

"mythread2" #12 prio=5 os_prio=0 tid=0x0000000058ef7800 nid=0x1ab4 waiting on condition [0x0000000059f8f000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000d602d610> (a java.util.concurrent.lock
s.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
errupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
bstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
tQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
ck.java:209)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)

        at DeathLock$2.run(DeathLock.java:34)

   Locked ownable synchronizers:
        - <0x00000000d602d640> (a java.util.concurrent.locks.ReentrantLock$Nonfa
irSync)

"mythread1" #11 prio=5 os_prio=0 tid=0x0000000058ef7000 nid=0x3e68 waiting on condition [0x000000005947f000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000d602d640> (a java.util.concurrent.lock
s.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
errupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
bstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
tQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
ck.java:209)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)

        at DeathLock$1.run(DeathLock.java:22)

   Locked ownable synchronizers:
        - <0x00000000d602d610> (a java.util.concurrent.locks.ReentrantLock$Nonfa
irSync)


Found one Java-level deadlock:
=============================
"mythread2":
  waiting for ownable synchronizer 0x00000000d602d610, (a java.util.concurrent.l
ocks.ReentrantLock$NonfairSync),
  which is held by "mythread1"
"mythread1":
  waiting for ownable synchronizer 0x00000000d602d640, (a java.util.concurrent.l
ocks.ReentrantLock$NonfairSync),
  which is held by "mythread2"

Java stack information for the threads listed above:
===================================================
"mythread2":
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000d602d610> (a java.util.concurrent.lock
s.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
errupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
bstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
tQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
ck.java:209)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)

        at DeathLock$2.run(DeathLock.java:34)
"mythread1":
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000d602d640> (a java.util.concurrent.lock
s.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
errupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
bstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
tQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
ck.java:209)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)

        at DeathLock$1.run(DeathLock.java:22)

Found 1 deadlock.
复制代码

可以根据日志输出来直接定位到具体的死锁代码。

2.jmc

jmc是Oracle Java Mission Control的缩写,是一个对Java程序进行管理、监控、概要分析和故障排查的工具套件。在JDK的bin目录中,同样是双击启动,如下图所示:

mc.PNG jmc打开如下:

11.PNG 需要选择死锁类,右键启动JMX控制台,然后就可以发现死锁和死锁具体信息。

常用工具还有jconsole,jvisualvm等不再一一介绍,感兴趣的可以自已查看一下。

避免死锁问题的发生

死锁分析

还要看死锁产生的4个条件,只要不满足条件就无法产生死锁了,互斥条件和不可剥夺条件是系统特性无法阻止。只能通过破坏请求和保持条件或者是环路等待条件,从而来解决死锁的问题。

避免死锁的方案

一、固定加锁顺序

即通过有顺序的获取锁,从而避免产生环路等待条件,从而解决死锁问题的。来看环路等待的例子:

22.PNG

线程1先获取了锁A,再请求获取锁B,线程2与线程1同时执行,线程2先获取锁B,再请求获取锁A,两个线程都先占用了各自的资源(锁A和锁B)之后,再尝试获取对方的锁,从而造成了环路等待问题,最后造成了死锁。

此时只需将线程1和线程2获取锁的顺序进行统一,也就是线程1和线程2同时执行之后,都先获取锁A,再获取锁B。因为只有一个线程能成功获取到锁A,没有获取到锁A的线程就会等待先获取锁A,此时得到锁A的线程继续获取锁 B,因为没有线程争抢和拥有锁B,那么得到锁A的线程就会顺利的拥有锁B,之后执行相应的代码再将锁资源全部释放,然后另一个等待获取锁A的线程就可以成功获取到锁资源,这样就不会出现死锁的问题了。

二、开放调用避免死锁

在协作对象之间发生死锁的场景中,主要是因为在调用某个方法时就需要持有锁,并且在方法内部也调用了其他带锁的方法,如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用,同步代码块最好仅被用于保护那些涉及共享状态的操作。

三、使用定时锁

使用显式Lock锁,在获取锁时使用tryLock()方法。当等待超过时限的时候,tryLock()不会一直等待,而是返回错误信息,能够有效避免死锁问题。

总结

简单来说,死锁问题的产生是由两个或者以上线程并行执行的时候,争夺资源而互相等待造成的。

死锁只有同时满足互斥、持有并等待、不可剥夺、环路等待这四个条件的时候才会发生。

所以要避免死锁问题,就是要破坏其中一个条件即可,最常用的方法就是使用资源有序分配法来破坏环路等待条件。

抽奖说明

1.本活动由掘金官方支持 详情可见juejin.cn/post/701221…

2.通过评论和文章有关的内容即可参加,要和文章内容有关哦!

3.本月的文章都会参与抽奖活动,欢迎大家多多互动!

4.除掘金官方抽奖外本人也将送出周边礼物(马克杯一个和掘金徽章若干,马克杯将送给走心评论,徽章随机抽取,数量视评论人数增加)。

猜你喜欢

转载自juejin.im/post/7017913954189967391