美文网首页
面试题:谈谈对死锁的看法?

面试题:谈谈对死锁的看法?

作者: 布朗XD | 来源:发表于2020-12-12 18:49 被阅读0次

    什么是死锁?

    所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁1再获得锁2的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁2再锁1的顺序获得锁。如下图所示:


    lock.png

    我们可以在JAVA代码中模拟一下死锁的情况:

    /**
     * @author brianxia
     * @version 1.0
     * @date 2020/12/12 18:18
     */
    public class DeadLock {
    
        public static void main(String[] args) {
            //创建两个资源文件用于加锁
            Object obj1 = new Object();
            Object obj2 = new Object();
    
            new Thread(() -> {
                //第一个线程,先拿到obj1,对obj1加锁
                synchronized (obj1) {
                    try {
                        //休眠的作用是防止连续拿到两个资源
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj2) {
    
                    }
                }
            }).start();
    
            new Thread(() -> {
                //第二个线程,先拿到obj2,对obj1加锁
                synchronized (obj2) {
                    try {
                        //休眠的作用是防止连续拿到两个资源
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (obj1) {
    
                    }
                }
            }).start();
        }
    }
    
    

    上述代码模拟了最开始说明的情况:

    • 线程A对obj1加锁
    • 线程B对obj2加锁
    • 线程A想要获取obj2的锁
    • 线程B想要获取obj1的锁
      双方互不让步,最终导致了死锁。

    检测死锁

    在JAVA中,可以使用jps+jstack命令检测死锁:
    首先使用jps找到对应的进程ID,比如这个案例中,我的进程名字和类名是一致的DeadLock。

    >jps
    
    20512 QuorumPeerMain
    10820 jar
    15076 RemoteMavenServer36
    22244 RemoteMavenServer36
    14056 RemoteMavenServer36
    21032 RemoteMavenServer36
    26504
    21036 RemoteMavenServer36
    33712 DeadLock
    34068 RemoteMavenServer36
    21276 Launcher
    30652 Jps
    7196
    
    

    接下来使用jstack分析线程运行情况:

    jstack 33712
    2020-12-12 18:21:06
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode):
    
    "DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000000002bbe800 nid=0x8e88 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Thread-1" #13 prio=5 os_prio=0 tid=0x000000001fc31800 nid=0x24a4 waiting for monitor entry [0x00000000205be000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at DeadLock.lambda$main$1(DeadLock.java:35)
            - waiting to lock <0x000000076c112c80> (a java.lang.Object)
            - locked <0x000000076c112c90> (a java.lang.Object)
            at DeadLock$$Lambda$2/1096979270.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    
    "Thread-0" #12 prio=5 os_prio=0 tid=0x000000001fc2f800 nid=0x81b4 waiting for monitor entry [0x00000000204bf000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at DeadLock.lambda$main$0(DeadLock.java:21)
            - waiting to lock <0x000000076c112c90> (a java.lang.Object)
            - locked <0x000000076c112c80> (a java.lang.Object)
            at DeadLock$$Lambda$1/1324119927.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    
    "Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001e061000 nid=0x5ad4 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001dfc8000 nid=0x8fd8 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001dfba800 nid=0x3cc4 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001dfb9800 nid=0x21bc waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001dfa3000 nid=0x4834 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001dfa6800 nid=0x6480 runnable [0x000000001f5be000]
       java.lang.Thread.State: RUNNABLE
            at java.net.SocketInputStream.socketRead0(Native Method)
            at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
            at java.net.SocketInputStream.read(SocketInputStream.java:171)
            at java.net.SocketInputStream.read(SocketInputStream.java:141)
            at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
            at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
            at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
            - locked <0x000000076c24a6d8> (a java.io.InputStreamReader)
            at java.io.InputStreamReader.read(InputStreamReader.java:184)
            at java.io.BufferedReader.fill(BufferedReader.java:161)
            at java.io.BufferedReader.readLine(BufferedReader.java:324)
            - locked <0x000000076c24a6d8> (a java.io.InputStreamReader)
            at java.io.BufferedReader.readLine(BufferedReader.java:389)
            at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)
    
    "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001df13000 nid=0x6698 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001df6a000 nid=0x872c runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001def3000 nid=0x3140 in Object.wait() [0x000000001f25f000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x000000076bf88ed0> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
            - locked <0x000000076bf88ed0> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
            at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
    
    "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000001c7fc800 nid=0x620 in Object.wait() [0x000000001f15e000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x000000076bf86bf8> (a java.lang.ref.Reference$Lock)
            at java.lang.Object.wait(Object.java:502)
            at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
            - locked <0x000000076bf86bf8> (a java.lang.ref.Reference$Lock)
            at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
    
    "VM Thread" os_prio=2 tid=0x000000001c7f7000 nid=0x2500 runnable
    
    "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002bd8800 nid=0x8f80 runnable
    
    "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002bda000 nid=0x2a5c runnable
    
    "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002bdb800 nid=0x4b80 runnable
    
    "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002bdd000 nid=0x3b24 runnable
    
    "GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002be0800 nid=0x831c runnable
    
    "GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002be1800 nid=0x4330 runnable
    
    "GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002be5000 nid=0x8654 runnable
    
    "GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002be6000 nid=0x824c runnable
    
    "GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002be7000 nid=0x7214 runnable
    
    "GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002be8800 nid=0x8474 runnable
    
    "VM Periodic Task Thread" os_prio=2 tid=0x000000001e074000 nid=0x32e8 waiting on condition
    
    JNI global references: 316
    
    
    Found one Java-level deadlock:
    =============================
    "Thread-1":
      waiting to lock monitor 0x000000001c803628 (object 0x000000076c112c80, a java.lang.Object),
      which is held by "Thread-0"
    "Thread-0":
      waiting to lock monitor 0x000000001c800d98 (object 0x000000076c112c90, a java.lang.Object),
      which is held by "Thread-1"
    
    Java stack information for the threads listed above:
    ===================================================
    "Thread-1":
            at DeadLock.lambda$main$1(DeadLock.java:35)
            - waiting to lock <0x000000076c112c80> (a java.lang.Object)
            - locked <0x000000076c112c90> (a java.lang.Object)
            at DeadLock$$Lambda$2/1096979270.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    "Thread-0":
            at DeadLock.lambda$main$0(DeadLock.java:21)
            - waiting to lock <0x000000076c112c90> (a java.lang.Object)
            - locked <0x000000076c112c80> (a java.lang.Object)
            at DeadLock$$Lambda$1/1324119927.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    
    Found 1 deadlock.
    
    

    重点来看最后一段话:


    image.png

    jstack命令已经发现了一个死锁,同时在下方标注了锁的ID。

    • Thread-0拥有0x000000076c112c80锁,等待0x000000076c112c90锁。
    • Thread-1拥有0x000000076c112c90锁,等待0x000000076c112c80锁。
      这样就发生了死锁。

    死锁产生的4个必要条件?

    • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。

    案例中线程1一直持有obj1的锁,线程2一直持有obj2的锁

    • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。

    线程均不释放锁

    • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。

    无法强制释放锁

    • 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。

    解决死锁的基本方法

    • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
      只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
    • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
    • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

    这里具体说一下资源有序分配法,这是原来的分配方式:


    image.png

    修改后的:


    image.png

    这样就不会产生死锁了。

    /**
     * @author brianxia
     * @version 1.0
     * @date 2020/12/12 18:18
     */
    public class DeadLock {
    
        public static void main(String[] args) {
            //创建两个资源文件用于加锁
            Object obj1 = new Object();
            Object obj2 = new Object();
    
            new Thread(() -> {
                //第一个线程,先拿到obj1,对obj1加锁
                synchronized (obj1) {
                    try {
                        //休眠的作用是防止连续拿到两个资源
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj2) {
    
                    }
                }
            }).start();
    
            new Thread(() -> {
                //第二个线程,先拿到obj1,对obj2加锁,按照有序的方式解决死锁
                synchronized (obj1) {
                    try {
                        //休眠的作用是防止连续拿到两个资源
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (obj2) {
    
                    }
                }
            }).start();
        }
    }
    
    

    当然还有一种方式是使用lock进行超时取消获取锁:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author brianxia
     * @version 1.0
     * @date 2020/12/12 18:18
     */
    public class DeadLock2 {
    
        public static void main(String[] args) {
            //创建两个资源文件用于加锁
            ReentrantLock reentrantLock1 = new ReentrantLock();
            ReentrantLock reentrantLock2 = new ReentrantLock();
    
            new Thread(() -> {
                reentrantLock1.lock();
                try {
                    //休眠的作用是防止连续拿到两个资源
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
    
                try {
                    if (reentrantLock2.tryLock(200, TimeUnit.MILLISECONDS)) {
                        System.out.println("线程1拿到锁2");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                reentrantLock1.unlock();
                System.out.println("线程1解锁1");
    
            }).start();
    
            new Thread(() -> {
                reentrantLock2.lock();
                try {
                    //休眠的作用是防止连续拿到两个资源
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    if (reentrantLock1.tryLock(200, TimeUnit.MILLISECONDS)) {
                        System.out.println("线程2拿到锁1");
                    }
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                reentrantLock2.unlock();
                System.out.println("线程2解锁2");
    
            }).start();
        }
    }
    
    

    相关文章

      网友评论

          本文标题:面试题:谈谈对死锁的看法?

          本文链接:https://www.haomeiwen.com/subject/qnkygktx.html