JAVA工程师常见面试题(一):谈谈对死锁的看法?

什么是死锁?

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

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

推荐阅读更多精彩内容