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

2020-12-12  本文已影响0人  布朗XD

什么是死锁?

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程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();
    }
}

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

检测死锁

在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。

死锁产生的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();
    }
}

上一篇 下一篇

猜你喜欢

热点阅读