zoukankan      html  css  js  c++  java
  • [锁] 线程死锁解析

    一  前言

      在 Java 的并发编程中,有一个问题需要特别注意,那就是死锁,如果发生了死锁,基本就是重启,而重启将会丢失运行中的数据。所以,了解死锁的形成并排查死锁到预防死锁成了一个重要的问题。

      我们了解任何一个事情的步骤是:what,how,why,why not。

    二  什么是死锁

    /**
     * 两个线程相互得到锁1,锁2,然后线程1等待线程2释放锁2,线程2等待线程1释放锁1,两者各不相互,这样形成死锁。
     * 那么如何避免和解决死锁问题呢?
     * 1、按顺序加锁
     *      上个例子线程间加锁的顺序各不一致,导致死锁,如果每个线程都按同一个的加锁顺序这样就不会出现死锁。
     * 2、获取锁时限
     *      每个获取锁的时候加上个时限,如果超过某个时间就放弃获取锁之类的。
     * 3、死锁检测
     *      按线程间获取锁的关系检测线程间是否发生死锁,如果发生死锁就执行一定的策略,如终断线程或回滚操作等。
     */
    public class DeadThreadTest {
        private static final Object lock1 = new Object();
        private static final Object lock2 = new Object();
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {//非lamada表达式
                @Override
                public void run() {
                    synchronized (lock1){
                        System.out.println(Thread.currentThread().getName() + " get lock1");
                        try {
                            Thread.sleep(100);//休眠100ms,保证其他线程能先获取lock2
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (lock2){
                            System.out.println(Thread.currentThread().getName() + " get lock2");
                        }
                    }
                }
            });
            thread1.setName("thread1");
            thread1.start();
    
            Thread thread2 = new Thread(()->{
                synchronized (lock2){
                    System.out.println(Thread.currentThread().getName() + " get lock2");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (lock1){
                        System.out.println(Thread.currentThread().getName() + " get lock1");
                    }
                }
            });
            thread2.setName("thread2");
            thread2.start();
        }
    }

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

      像我们现在写的代码是自己故意造出来的死锁,我们能够发现,那如果是线上环境怎么办,假如我们的系统卡死了,我们怎么知道到底是哪一段代码出现了问题,有没有可能使死锁的问题。也就是如何检测死锁。

    三  如何检测死锁

      由于死锁极难通过人工的方式查出来,因此JDK 提供了命令来检测某个java进程中线程的情况,并排查有没有死锁。

      jps:用来查看java 程序的进程号,当然在 Linux 中也可以通过别的方式获取

      jstack pid:进程号命令则可以打印对应进程的栈信息,并找到死锁。

      我们就刚刚的程序,在 windows 上使用该命令。

    jps
    10832 Launcher
    14272 Launcher
    18560 Launcher
    22672 Jps
    24336 Launcher
    10884 DeadThreadTest

      使用jstack打印进程的堆栈信息:

    D:IdeaProjectsmyProjectmySystem>jstack 10884
    2019-10-13 17:41:41
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode):
    
    "DestroyJavaVM" #15 prio=5 os_prio=0 tid=0x0000000002f8c000 nid=0x37a4 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "thread2" #14 prio=5 os_prio=0 tid=0x000000001b853000 nid=0x3e40 waiting for monitor entry [0x000000001c8be000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at com.dh.yjt.SpringBootDemo.test.concurrent.DeadThreadTest.lambda$main$0(DeadThreadTest.java:46)
            - waiting to lock <0x00000000d7e36558> (a java.lang.Object)
            - locked <0x00000000d7e36568> (a java.lang.Object)
            at com.dh.yjt.SpringBootDemo.test.concurrent.DeadThreadTest$$Lambda$1/885851948.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    
    "thread1" #13 prio=5 os_prio=0 tid=0x000000001a253800 nid=0x327c waiting for monitor entry [0x000000001b50f000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at com.dh.yjt.SpringBootDemo.test.concurrent.DeadThreadTest$1.run(DeadThreadTest.java:29)
            - waiting to lock <0x00000000d7e36568> (a java.lang.Object)
            - locked <0x00000000d7e36558> (a java.lang.Object)
            at java.lang.Thread.run(Thread.java:748)
    
    "Service Thread" #12 daemon prio=9 os_prio=0 tid=0x000000001a236800 nid=0x5c9c runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C1 CompilerThread2" #11 daemon prio=9 os_prio=2 tid=0x000000001a18c000 nid=0xffc waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread1" #10 daemon prio=9 os_prio=2 tid=0x000000001a18b800 nid=0x4cd8 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread0" #9 daemon prio=9 os_prio=2 tid=0x000000001a10a800 nid=0x6b8 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "JDWP Command Reader" #8 daemon prio=10 os_prio=0 tid=0x0000000017ac3800 nid=0x50b4 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "JDWP Event Helper Thread" #7 daemon prio=10 os_prio=0 tid=0x0000000017ac0000 nid=0x23c4 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "JDWP Transport Listener: dt_socket" #6 daemon prio=10 os_prio=0 tid=0x0000000017ab3000 nid=0x3434 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000018e82800 nid=0x3344 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000017a93000 nid=0x1884 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000017a5e800 nid=0x51ac in Object.wait() [0x0000000018ddf000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x00000000d7508ec8> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
            - locked <0x00000000d7508ec8> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
            at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
    
    "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000000307e000 nid=0x3d74 in Object.wait() [0x0000000018cdf000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x00000000d7506b68> (a java.lang.ref.Reference$Lock)
            at java.lang.Object.wait(Object.java:502)
            at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
            - locked <0x00000000d7506b68> (a java.lang.ref.Reference$Lock)
            at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
    
    "VM Thread" os_prio=2 tid=0x0000000017a37800 nid=0x4008 runnable
    
    "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002fa5000 nid=0x2f08 runnable
    
    "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002fa6800 nid=0x43fc runnable
    
    "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002fa8000 nid=0x2284 runnable
    
    "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002fa9800 nid=0x47fc runnable
    
    "VM Periodic Task Thread" os_prio=2 tid=0x000000001a196800 nid=0x1dec waiting on condition
    
    JNI global references: 2460
    
    
    Found one Java-level deadlock:
    =============================
    "thread2":
      waiting to lock monitor 0x0000000017a6c8b8 (object 0x00000000d7e36558, a java.lang.Object),
      which is held by "thread1"
    "thread1":
      waiting to lock monitor 0x0000000017a6b368 (object 0x00000000d7e36568, a java.lang.Object),
      which is held by "thread2"
    
    Java stack information for the threads listed above:
    ===================================================
    "thread2":
            at com.dh.yjt.SpringBootDemo.test.concurrent.DeadThreadTest.lambda$main$0(DeadThreadTest.java:46)
            - waiting to lock <0x00000000d7e36558> (a java.lang.Object)
            - locked <0x00000000d7e36568> (a java.lang.Object)
            at com.dh.yjt.SpringBootDemo.test.concurrent.DeadThreadTest$$Lambda$1/885851948.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)
    "thread1":
            at com.dh.yjt.SpringBootDemo.test.concurrent.DeadThreadTest$1.run(DeadThreadTest.java:29)
            - waiting to lock <0x00000000d7e36568> (a java.lang.Object)
            - locked <0x00000000d7e36558> (a java.lang.Object)
            at java.lang.Thread.run(Thread.java:748)
    
    Found 1 deadlock.

      我们首先使用 jps 命令找到 java 进程号,然后使用 jstack 进程号 打印进程栈的信息,其中,在最后的部分,jstack 告诉我们,他找到了一个死锁,其中又详细的信息:Thread-2 线程持有 Object 类型的编号为 0x00000000d7e36568 的锁,等待编号为 0x00000000d7e36558 的锁 , 但这个锁由 Thread-1 持有,于此同时,Thread-1 和 Thread-2 相反。Thread-1 线程持有 0x00000000d7e36558 的锁,等待 0x00000000d7e36568 的锁。因此发生死锁。

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

    四  死锁形成的原因

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

    1. 互斥条件:一个资源每次只能被一个线程使用。

    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

    3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

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

      如果想要打破互斥条件,我们需要允许进程同时访问某些资源,这种方法受制于实际场景,不太容易实现条件;

      打破不可剥夺条件,这样需要允许进程强行从占有者那里夺取某些资源,或者简单一点理解,占有资源的进程不能再申请占有其他资源,必须释放手上的资源之后才能发起申请,这个其实也很难找到适用场景;

      进程在运行前申请得到所有的资源,否则该进程不能进入准备执行状态。这个方法看似有点用处,但是它的缺点是可能导致资源利用率和进程并发性降低;

      避免出现资源申请环路,即对资源事先分类编号,按号分配。这种方式可以有效提高资源的利用率和系统吞吐量,但是增加了系统开销,增大了进程对资源的占用时间。

  • 相关阅读:
    面试知识点2
    面试知识点3
    面试知识记录
    JQuery手写一个简单的轮播图
    推荐一款好用的日历插件
    JQuery获取复选框的值
    JQuery手写一个简单的分页
    JQuery给一个元素绑定两次点击事件(第二次点击事件)
    懒加载预加载(图片)
    JQuery Ajax 使用FormData上传文件对象
  • 原文地址:https://www.cnblogs.com/aiqiqi/p/11667417.html
Copyright © 2011-2022 走看看