死锁

1.什么是死锁?

package com.ljh2.test;

/**
 * @author liang_jiang_hao
 * @date 2021/3/30
 */
public class test2 {
    Object obj1=new Object();
    Object obj2=new Object();
    public static void main(String[] args) throws InterruptedException {
        test2 test = new test2();
        new Thread(()->{
            try {
                test.deadLock1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                test.deadLock2();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        Thread.sleep(50000);
    }

    public void deadLock1() throws InterruptedException {
        synchronized (obj1){
            obj1.notify();
            System.out.println(Thread.currentThread().getName()+
                    "拿到了obj1锁对象,继续执行准备拿obj2锁对象");
            Thread.sleep(1000);
            deadLock2();
            obj1.wait();
        }
    }
    public void deadLock2() throws InterruptedException {
        synchronized (obj2){
            obj2.notify();
            System.out.println(Thread.currentThread().getName()+
                    "拿到了obj2锁对象,继续执行准备拿obj1锁对象");
            Thread.sleep(1000);
            deadLock1();
            obj2.wait();
        }
    }
}

程序输出:

Thread-0拿到了obj1锁对象,继续执行准备拿obj2锁对象
Thread-1拿到了obj2锁对象,继续执行准备拿obj1锁对象

Process finished with exit code -1

上面的代码中,我们启用了两个线程,分别抢占2个资源,但这两个资源又分别被不同的对象锁住了。当第一个线程调用 deadLock1方法,进入同步块,拿到锁obj1,并等待 1 秒钟让另一个线程进入 deadLock2方法,当第二个线程进入同步块后,注意:此时, 拿着 obj1 锁的线程企图拿到 obj2 的锁,但这个时候,拿着 obj2 的线程也想去拿obj1 的锁。于是就出现了互相僵持的情况,谁也无法拿到对方的锁,整个系统就卡死了。

这种情况就是死锁。

2. 如何检测死锁?

由于死锁极难通过人工的方式查出来,因此JDK 提供了命令来检测某个java进程中心线程的情况,并排查有没有死锁。什么命令呢? jps , 用来查看java 程序的进程号,当然在 Linux 中也可以通过别的方式获取, jstack 进程号命令则可以答应对应进程的栈信息,并找到死锁。

C:\Users\ljh2>jps
23520 test2
25384 Jps
11852
25084 Launcher

C:\Users\ljh2>jstack 23520
2021-03-30 22:03:41
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.271-b09 mixed mode):

"Thread-1" #12 prio=5 os_prio=0 tid=0x0000020e7f9a1800 nid=0x6bc0 waiting for monitor entry [0x000000c4175ff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.ljh2.test.test2.deadLock1(test2.java:31)
        - waiting to lock <0x000000076b627750> (a java.lang.Object)
        at com.ljh2.test.test2.deadLock2(test2.java:45)
        - locked <0x000000076b627760> (a java.lang.Object)
        at com.ljh2.test.test2.lambda$main$1(test2.java:21)
        at com.ljh2.test.test2$$Lambda$2/1480010240.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"Thread-0" #11 prio=5 os_prio=0 tid=0x0000020e7f99e000 nid=0x3f84 waiting for monitor entry [0x000000c4174fe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.ljh2.test.test2.deadLock2(test2.java:41)
        - waiting to lock <0x000000076b627760> (a java.lang.Object)
        at com.ljh2.test.test2.deadLock1(test2.java:35)
        - locked <0x000000076b627750> (a java.lang.Object)
        at com.ljh2.test.test2.lambda$main$0(test2.java:14)
        at com.ljh2.test.test2$$Lambda$1/999966131.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x0000020e7f6dc000 nid=0x57dc runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x0000020e7ec5c800 nid=0x69a0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000020e7ec48000 nid=0x6970 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000020e7ec44000 nid=0x12e0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000020e7ec42800 nid=0x66cc runnable [0x000000c416efe000]
   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 <0x000000076b512198> (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 <0x000000076b512198> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:47)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000020e7eba8000 nid=0x2f00 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000020e7eb56000 nid=0x6a84 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000020e7e3fc800 nid=0x294 in Object.wait() [0x000000c416bfe000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b388ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x000000076b388ee0> (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=0x0000020e7eb26000 nid=0x44ac in Object.wait() [0x000000c416aff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b386c00> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x000000076b386c00> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"main" #1 prio=5 os_prio=0 tid=0x0000020e72a08800 nid=0x1bb0 waiting on condition [0x000000c4164ff000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.ljh2.test.test2.main(test2.java:26)

"VM Thread" os_prio=2 tid=0x0000020e7eb01000 nid=0x6874 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000020e72a20800 nid=0x60ac runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000020e72a21800 nid=0x1284 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000020e72a23000 nid=0x6654 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000020e72a24800 nid=0x5c44 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x0000020e7f724800 nid=0x6680 waiting on condition

JNI global references: 316


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000020e7eb2c518 (object 0x000000076b627750, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000020e7eb2c678 (object 0x000000076b627760, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.ljh2.test.test2.deadLock1(test2.java:31)
        - waiting to lock <0x000000076b627750> (a java.lang.Object)
        at com.ljh2.test.test2.deadLock2(test2.java:45)
        - locked <0x000000076b627760> (a java.lang.Object)
        at com.ljh2.test.test2.lambda$main$1(test2.java:21)
        at com.ljh2.test.test2$$Lambda$2/1480010240.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at com.ljh2.test.test2.deadLock2(test2.java:41)
        - waiting to lock <0x000000076b627760> (a java.lang.Object)
        at com.ljh2.test.test2.deadLock1(test2.java:35)
        - locked <0x000000076b627750> (a java.lang.Object)
        at com.ljh2.test.test2.lambda$main$0(test2.java:14)
        at com.ljh2.test.test2$$Lambda$1/999966131.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.


C:\Users\ljh2>

“Thread-1”:
at com.ljh2.test.test2.deadLock1(test2.java:31)
- waiting to lock <0x000000076b627750> (a java.lang.Object)
at com.ljh2.test.test2.deadLock2(test2.java:45)
- locked <0x000000076b627760> (a java.lang.Object)

“Thread-0”:
at com.ljh2.test.test2.deadLock2(test2.java:41)
- waiting to lock <0x000000076b627760> (a java.lang.Object)
at com.ljh2.test.test2.deadLock1(test2.java:35)
- locked <0x000000076b627750> (a java.lang.Object)

我们首先使用 jps 命令找到 java 进程号,然后使用 jstack 进程号 打印进程栈的信息,其中,在最后的部分,jstack 告诉我们,他找到了一个死锁,其中又详细的信息:Thread-1 线程(这里我们没有给线程其合适的名字,如果在线上,给线程起一个合适的名字将更有利于排查)持有 Object类型的编号为 0x000000076b627760的锁,等待编号为 0x000000076b627750的锁 , 但这个锁由 Thread-0 持有,于此同时,Thread-0 和 Thread-1 相反。Thread-0 线程持有 0x000000076b627750的锁,等待 0x000000076b627760 的锁。我们的注释里也写上了。

那么发生了死锁,该怎么办呢?最简单的办法就是重启,重启之后,对 jstack 中打印的堆栈信息中的代码进行修改。重新发布。当然还有一些高级策略,比如让进程回滚到死锁前的状态,然后让他们顺序进入同步块。

3. 死锁有哪些形成的原因

一般来说,要出现死锁问题需要满足以下条件:

  1. 互斥条件:涉及的资源是非共享的。
  2. 请求和保持条件:进程在等待一新资源时继续占有已分配的资源。
  3. 不剥夺条件:不能强行剥夺进程拥有的资源。
  4. 环路等待条件:存在一种进程的循环链,链中的每一个进程已获得的资源同时被链中的下一个进程所请求。

死锁是由四个必要条件导致的,所以一般来说,只要破坏这四个必要条件中的一个条件,死锁情况就应该不会发生。

处理死锁的方法

一、预防死锁

基本思想:打破产生死锁的四个必要条件中的一个或几个。

1. 摒弃“请求和保持”条件 系统要求所有进程在开始运行之前,要一次性地申请在整个运行过程中所需的全部资源。若系统有足够资源则完全分配;否则,进程等待。

优点:

  • 简单、易于实现且安全。

缺点:

  • 实用性不强:在许多情况下,一个进程在执行之前不可能知道它所需要的全部资源。这是由于进程在执行时是动态的,不可预测的;
  • 资源利用率低:无论所分资源何时用到,一个进程只有在占有所需的全部资源后才能执行。即使有些资源最后才被该进程用到一次,但该进程在生存期间却一直占有它们,造成长期占着不用的状况。造成极大的资源浪费;
  • 延迟进程的运行:因为进程只有获得了其所需的全部资源才开始运行,但有些资源可能长期被其它进程占用,导致该进程迟迟不能运行。

2.摒弃“不剥夺”条件 一 个已拥有资源的进程,若它再提出新资源要求而不能立即得到满足时,它必须释放已经拥有的所有资源。以后需要时再重新申请。

缺点:实现复杂、要付出很大的代价。原因:

  • 一个资源在使用一段时间后被释放,可能会造成前阶段工作的失效;
  • 反复地申请和释放资源,又会使进程的执行无限推迟,从而进一步增加系统的开销,降低系统的吞吐量。

3.摒弃“环路等待”条件 系统中的所有资源都有一个确定的唯一号码,所有分配请求必须以序号上升的次序进行。

例如:系统中有下列设备:输入机(1),打印机(2),穿孔机(3),磁带机(4),磁盘(5)。有一进程要先后使用输入机、磁盘、打印机,则它申请设备时要按输入机、打印机、磁盘的顺序申请。

优点:

  • 同前两法相比,其资源利用率和系统吞吐量有较明显的改善。

缺点:

  • 系统中各类资源的编号必须相对稳定,限制了新设备类型的增加;
  • 进程实际需要资源的顺序不一定与资源的编号一致,因此仍会造成资源浪费。
二、避免死锁
	在系统运行过程中,对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,若分配后系统可能发生死锁,则不予分配,否则予以分配。
	把系统的状态分为安全状态和不安全状态,只要能使系统始终都处于安全状态,便可避免死锁的发生。

避免死锁的本质在于: 当进行资源分配时,如何避免进入不安全状态。

利用银行家算法避免死锁

银行家算法中的数据结构:

  • 可利用资源向量Available。它是一个含有m个元素的数组,其中每个元素代表一类可利用资源的数目。
  • 最大需求矩阵Max。n*m矩阵,表示n个进程的每一个对m类资源的最大需求。
  • 分配矩阵Allocation。n*m矩阵,表示每个进程分配的资源数。
  • 需求矩阵Need。n*m矩阵,表示每个进程还需要各类资源数。

当进程pi提出资源申请时,系统执行下列步骤:
(1) 若Request[i]≤Need[i],转(2);否则错误返回。
(2) 若Request[i]≤Available,转(3);否则进程等待。
(3) 假设系统分配了资源,则有:
Available:=Available-Request[i];
Allocation[i]:=Allocation[i]+Request[i];
Need[i]:=Need[i]-Request[i]
(4) 执行安全性算法,若系统新状态是安全的,则分配完成;若系统新状态是不安全的,则恢复原状态,进程等待。

三、死锁的检测与解除

死锁检测 允许死锁发生,操作系统不断监视系统进展情况,判断死锁是否发生。

一旦死锁发生则采取专门的措施,解除死锁并以最小的代价恢复操作系统运行。

死锁解除

剥夺资源

强迫从其它进程剥夺足够数量的资源给死锁进程,直至死锁不存在。

撤消进程

终止参与死锁的进程,收回它们占有的资源,从而解除死锁。又分两种情况:

  • 一次性撤消参与死锁的全部进程,剥夺全部资源。
  • 逐步撤消参与死锁的进程,直至有足够的资源可用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值