死锁

死锁的定义

      两个或两个以上的进程因竞争资源,而处于互相等待的状态,如果没有外力作用,他们都将无法推进下去。典型例子就是哲学家进餐问题。

    举个例子

       上面的内容可能有些抽象,因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。如下图所示:

我们用一段代码来模拟上述过程:

@Slf4j
public class DemoLock  {

    public static void main(String[] args) {
        final   Object a = new Object();
        final   Object b = new Object();

        Thread threadA = new Thread(() -> {
            synchronized (a) {
                try {
                    System.out.println("now  threadA-locka");
                    Thread.sleep(1000l);
                    synchronized (b) {
                        System.out.println("now  threadA-lockb");
                    }
                } catch (Exception e) {
                    // ignore
                }
            }

        });

        Thread threadB = new Thread(()->{
            synchronized (b) {
                try {
                    System.out.println("now  threadB-lockb");
                    Thread.sleep(1000l);
                    synchronized (a) {
                        System.out.println("now  threadB-locka");
                    }
                } catch (Exception e) {
                    // ignore
                }
            }
        });
        threadA.start();
        threadB.start();
    }

}

程序执行结果如下:

 

很明显,程序执行停滞了。

死锁发生必须具备的必要条件

         互斥条件(某个资源只能有一个线程持有)

         请求和保持条件(对自己获得的资源保持不放,又去请求新资源)

         不剥夺条件(线程已经持有的资源不能被剥夺,必须自己释放)

         环路等待(发生死锁时一定有一个进程请求的资源构成一个环形的链)

避免死锁的方案

           1、以特定顺序获得锁,尝试按照锁对象的HashCode值大小的顺序,分别获得两个锁,这样总会以特定顺序获取锁。

           2、超时放弃,当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。

            3、死锁预防检测的银行家算法(比较复杂不建议使用)

   java中死锁的检测的方法和工具

    在这里,我将介绍两种死锁检测工具

    1、Jstack命令

       jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照线程快照是当前java虚拟机内每一条线程正在执行方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁死循环请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

首先,我们通过jps确定当前执行任务的进程号:

-Air:~$ jps
48672 Launcher
48673 DemoLock
42916 JConsole
3415 
48778 Jps
22843 Launcher
10366 Launcher
1151 Launcher

可以确定任务进程号是48673,然后执行jstack命令查看当前进程堆栈信息:

Air:~$ jstack -F 48673
Attaching to process ID 48673, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
Deadlock Detection:

Found one Java-level deadlock:
=============================

"Thread-0":
  waiting to lock Monitor@0x00007fd9a70196b8 (Object@0x00000007960e39d0, a java/lang/Object),
  which is held by "Thread-1"
"Thread-1":
  waiting to lock Monitor@0x00007fd9a701bf48 (Object@0x00000007960e39c0, a java/lang/Object),
  which is held by "Thread-0"

Found a total of 1 deadlock.

Thread 5891: (state = BLOCKED)


Thread 15107: (state = BLOCKED)
 - com.clubfactory.sc.core.utils.DemoLock.lambda$main$1(java.lang.Object, java.lang.Object) @bci=22, line=33 (Interpreted frame)
 - com.clubfactory.sc.core.utils.DemoLock$$Lambda$2.run() @bci=8 (Interpreted frame)
 - java.lang.Thread.run() @bci=11, line=748 (Interpreted frame)


Thread 14851: (state = BLOCKED)
 - com.clubfactory.sc.core.utils.DemoLock.lambda$main$0(java.lang.Object, java.lang.Object) @bci=22, line=18 (Interpreted frame)
 - com.clubfactory.sc.core.utils.DemoLock$$Lambda$1.run() @bci=8 (Interpreted frame)
 - java.lang.Thread.run() @bci=11, line=748 (Interpreted frame)


Thread 13827: (state = BLOCKED)


Thread 12291: (state = BLOCKED)
 - java.lang.Object.wait(long) @bci=0 (Interpreted frame)
 - java.lang.ref.ReferenceQueue.remove(long) @bci=59, line=143 (Interpreted frame)
 - java.lang.ref.ReferenceQueue.remove() @bci=2, line=164 (Interpreted frame)
 - java.lang.ref.Finalizer$FinalizerThread.run() @bci=36, line=209 (Interpreted frame)


Thread 12035: (state = BLOCKED)
 - java.lang.Object.wait(long) @bci=0 (Interpreted frame)
 - java.lang.Object.wait() @bci=2, line=502 (Interpreted frame)
 - java.lang.ref.Reference.tryHandlePending(boolean) @bci=54, line=191 (Interpreted frame)
 - java.lang.ref.Reference$ReferenceHandler.run() @bci=1, line=153 (Interpreted frame)

可以看到,进程的确存在死锁,两个线程分别在等待对方持有的Object对象

2、JConsole工具

      Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。

我们在命令行中敲入jconsole命令,会自动弹出以下对话框,选择进程48673,并点击“链接

 

进入所检测的进程后,选择“线程”选项卡,并点击“检测死锁”

 

可以看到以下画面:

 

可以看到进程中存在死锁。

       以上例子我都是用synchronized关键词实现的死锁,如果读者用ReentrantLock制造一次死锁,再次使用死锁检测工具,也同样能检测到死锁,不过显示的信息将会更加丰富,有兴趣的读者可以自己尝试一下。

posted @ 2019-11-14 15:13  老木木  阅读(85)  评论(0)    收藏  举报