Java多线程中的死锁详细介绍

synchronized关键字用于使类或方法具有线程安全性, 这意味着只有一个线程可以拥有同步方法的锁并可以使用它, 其他线程必须等到锁释放后才能由他们中的任何一个获取。
如果我们的程序在多线程环境中运行(其中两个或多个线程同时执行),那么使用它是很重要的。但有时也会导致死锁。下面是死锁条件的一个简单示例。

Java多线程中的死锁详细介绍

文章图片
Java
//Java program to illustrate Deadlock //in multithreading. class Util { //Util class to sleep a thread static void sleep( long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } } //This class is shared by both threads class Shared { //first synchronized method synchronized void test1(Shared s2) { System.out.println( "test1-begin" ); Util.sleep( 1000 ); //taking object lock of s2 enters //into test2 method s2.test2(); System.out.println( "test1-end" ); } //second synchronized method synchronized void test2() { System.out.println( "test2-begin" ); Util.sleep( 1000 ); //taking object lock of s1 enters //into test1 method System.out.println( "test2-end" ); } } class Thread1 extends Thread { private Shared s1; private Shared s2; //constructor to initialize fields public Thread1(Shared s1, Shared s2) { this .s1 = s1; this .s2 = s2; } //run method to start a thread @Override public void run() { //taking object lock of s1 enters //into test1 method s1.test1(s2); } } class Thread2 extends Thread { private Shared s1; private Shared s2; //constructor to initialize fields public Thread2(Shared s1, Shared s2) { this .s1 = s1; this .s2 = s2; } //run method to start a thread @Override public void run() { //taking object lock of s2 //enters into test2 method s2.test2(s1); } } public class Deadlock { public static void main(String[] args) { //creating one object Shared s1 = new Shared(); //creating second object Shared s2 = new Shared(); //creating first thread and starting it Thread1 t1 = new Thread1(s1, s2); t1.start(); //creating second thread and starting it Thread2 t2 = new Thread2(s1, s2); t2.start(); //sleeping main thread Util.sleep( 2000 ); } }

Output : test1-begin test2-begin

不建议使用在线IDE运行上述程序。我们可以复制源代码并在我们的本地计算机上运行它。我们可以看到它运行了不确定的时间, 因为线程处于死锁状态, 并且不允许代码执行。现在, 让我们一步一步看看那里发生了什么。
  1. 线程t1启动并通过获取s1的对象锁来调用test1方法。
  2. 线程t2启动并通过获取s2的对象锁来调用test2方法。
  3. t1开始打印test1-begin, t2开始打印test-2, 并且都等待1秒钟, 这样, 如果两个线程中的任何一个都没有, 则可以启动两个线程。
  4. t1尝试获取s2的对象锁并调用方法test2, 但是由于t2已经获取了它, 因此它等待直到它变得空闲为止。直到获得s2的锁定, 它才会释放s1的锁定。
  5. t2也是如此。它尝试获取s1的对象锁并调用方法test1, 但是它已被t1获取, 因此它必须等到t1释放锁。 t2也不会释放s2的锁定, 直到它获得s1的锁定。
  6. 现在, 两个线程都处于等待状态, 正在等待彼此释放锁。现在围绕条件争夺谁先释放锁。
  7. 由于它们都不准备释放锁定, 因此这是死锁状态。
  8. 当你运行该程序时, 看起来执行已暂停。
检测死锁状态
我们还可以通过在cmd上运行此程序来检测死锁。我们必须收集线程转储。收集命令取决于操作系统类型。如果我们使用Windows和Java 8, 则命令为jcmd $ PID Thread.print
我们可以通过运行jps命令获取PID。上面程序的线程转储如下:
jcmd 18692 Thread.print 18692: 2020-06-08 19:03:10 Full thread dump OpenJDK 64-Bit Server VM (11.0.4+10-b304.69 mixed mode, sharing):Threads class SMR info: _java_thread_list=0x0000017f44b69f20, length=13, elements={ 0x0000017f43f77000, 0x0000017f43f79800, 0x0000017f43f90000, 0x0000017f43f91000, 0x0000017f43f95000, 0x0000017f43fa5000, 0x0000017f43fb0800, 0x0000017f43f5b800, 0x0000017f44bc9000, 0x0000017f44afb000, 0x0000017f44bd7800, 0x0000017f44bd8800, 0x0000017f298c9000 }"Reference Handler" #2 daemon prio=10 os_prio=2 cpu=0.00ms elapsed=57.48s tid=0x0000017f43f77000 nid=0x6050 waiting on condition[0x0000005f800ff000] java.lang.Thread.State: RUNNABLE at java.lang.ref.Reference.waitForReferencePendingList(java.base@11.0.4/Native Method) at java.lang.ref.Reference.processPendingReferences(java.base@11.0.4/Reference.java:241) at java.lang.ref.Reference$ReferenceHandler.run(java.base@11.0.4/Reference.java:213)"Finalizer" #3 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=57.48s tid=0x0000017f43f79800 nid=0x2824 in Object.wait()[0x0000005f801fe000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(java.base@11.0.4/Native Method) - waiting on(a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:155) - waiting to re-lock in wait()(a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:176) at java.lang.ref.Finalizer$FinalizerThread.run(java.base@11.0.4/Finalizer.java:170)"Signal Dispatcher" #4 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=57.47s tid=0x0000017f43f90000 nid=0x1710 runnable[0x0000000000000000] java.lang.Thread.State: RUNNABLE"Attach Listener" #5 daemon prio=5 os_prio=2 cpu=31.25ms elapsed=57.47s tid=0x0000017f43f91000 nid=0x4ff4 waiting on condition[0x0000000000000000] java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 cpu=46.88ms elapsed=57.47s tid=0x0000017f43f95000 nid=0x350c waiting on condition[0x0000000000000000] java.lang.Thread.State: RUNNABLE No compile task"C1 CompilerThread0" #9 daemon prio=9 os_prio=2 cpu=93.75ms elapsed=57.47s tid=0x0000017f43fa5000 nid=0x4900 waiting on condition[0x0000000000000000] java.lang.Thread.State: RUNNABLE No compile task"Sweeper thread" #10 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=57.47s tid=0x0000017f43fb0800 nid=0x6120 runnable[0x0000000000000000] java.lang.Thread.State: RUNNABLE"Common-Cleaner" #11 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=57.44s tid=0x0000017f43f5b800 nid=0x5a4 in Object.wait()[0x0000005f807fe000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(java.base@11.0.4/Native Method) - waiting on(a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:155) - waiting to re-lock in wait()(a java.lang.ref.ReferenceQueue$Lock) at jdk.internal.ref.CleanerImpl.run(java.base@11.0.4/CleanerImpl.java:148) at java.lang.Thread.run(java.base@11.0.4/Thread.java:834) at jdk.internal.misc.InnocuousThread.run(java.base@11.0.4/InnocuousThread.java:134)"Monitor Ctrl-Break" #12 daemon prio=5 os_prio=0 cpu=15.63ms elapsed=57.36s tid=0x0000017f44bc9000 nid=0x5954 runnable[0x0000005f809fe000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(java.base@11.0.4/Native Method) at java.net.SocketInputStream.socketRead(java.base@11.0.4/SocketInputStream.java:115) at java.net.SocketInputStream.read(java.base@11.0.4/SocketInputStream.java:168) at java.net.SocketInputStream.read(java.base@11.0.4/SocketInputStream.java:140) at sun.nio.cs.StreamDecoder.readBytes(java.base@11.0.4/StreamDecoder.java:284) at sun.nio.cs.StreamDecoder.implRead(java.base@11.0.4/StreamDecoder.java:326) at sun.nio.cs.StreamDecoder.read(java.base@11.0.4/StreamDecoder.java:178) - locked(a java.io.InputStreamReader) at java.io.InputStreamReader.read(java.base@11.0.4/InputStreamReader.java:185) at java.io.BufferedReader.fill(java.base@11.0.4/BufferedReader.java:161) at java.io.BufferedReader.readLine(java.base@11.0.4/BufferedReader.java:326) - locked(a java.io.InputStreamReader) at java.io.BufferedReader.readLine(java.base@11.0.4/BufferedReader.java:392) at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)"Service Thread" #13 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=57.36s tid=0x0000017f44afb000 nid=0x6394 runnable[0x0000000000000000] java.lang.Thread.State: RUNNABLE"Thread-0" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=57.35s tid=0x0000017f44bd7800 nid=0x5304 waiting for monitor entry[0x0000005f80cfe000] java.lang.Thread.State: BLOCKED (on object monitor) at com.company.threads.Shared.test2(Deadlock.java:40) - waiting to lock(a com.company.threads.Shared) at com.company.threads.Shared.test1(Deadlock.java:33) - locked(a com.company.threads.Shared) at com.company.threads.Thread1.run(Deadlock.java:67)"Thread-1" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=57.35s tid=0x0000017f44bd8800 nid=0xfa4 waiting for monitor entry[0x0000005f80dfe000] java.lang.Thread.State: BLOCKED (on object monitor) at com.company.threads.Shared.test2(Deadlock.java:40) - waiting to lock(a com.company.threads.Shared) at com.company.threads.Shared.test1(Deadlock.java:33) - locked(a com.company.threads.Shared) at com.company.threads.Thread2.run(Deadlock.java:90)"DestroyJavaVM" #16 prio=5 os_prio=0 cpu=171.88ms elapsed=55.35s tid=0x0000017f298c9000 nid=0x38ec waiting on condition[0x0000000000000000] java.lang.Thread.State: RUNNABLE"VM Thread" os_prio=2 cpu=0.00ms elapsed=57.49s tid=0x0000017f43f73800 nid=0x52c4 runnable"GC Thread#0" os_prio=2 cpu=0.00ms elapsed=57.51s tid=0x0000017f298e1000 nid=0x47dc runnable"G1 Main Marker" os_prio=2 cpu=0.00ms elapsed=57.51s tid=0x0000017f29911000 nid=0x61c4 runnable"G1 Conc#0" os_prio=2 cpu=0.00ms elapsed=57.51s tid=0x0000017f29912000 nid=0x61c0 runnable"G1 Refine#0" os_prio=2 cpu=0.00ms elapsed=57.50s tid=0x0000017f43e0a800 nid=0x1fa8 runnable"G1 Young RemSet Sampling" os_prio=2 cpu=0.00ms elapsed=57.50s tid=0x0000017f43e0b000 nid=0x47a4 runnable "VM Periodic Task Thread" os_prio=2 cpu=0.00ms elapsed=57.36s tid=0x0000017f44b03800 nid=0x2408 waiting on conditionJNI global refs: 15, weak refs: 0Found one Java-level deadlock: ============================= "Thread-0": waiting to lock monitor 0x0000017f43f87980 (object 0x000000008a2e9ce0, a com.company.threads.Shared), which is held by "Thread-1" "Thread-1": waiting to lock monitor 0x0000017f43f87780 (object 0x000000008a2e9cd0, a com.company.threads.Shared), which is held by "Thread-0"Java stack information for the threads listed above: =================================================== "Thread-0": at com.company.threads.Shared.test2(Deadlock.java:40) - waiting to lock(a com.company.threads.Shared) at com.company.threads.Shared.test1(Deadlock.java:33) - locked(a com.company.threads.Shared) at com.company.threads.Thread1.run(Deadlock.java:67) "Thread-1": at com.company.threads.Shared.test2(Deadlock.java:40) - waiting to lock(a com.company.threads.Shared) at com.company.threads.Shared.test1(Deadlock.java:33) - locked(a com.company.threads.Shared) at com.company.threads.Thread2.run(Deadlock.java:90)Found 1 deadlock.

正如我们所看到的, 明确提到存在1个死锁。当你尝试使用计算机时, 可能会出现相同的消息。
【Java多线程中的死锁详细介绍】避免死锁状态
我们可以通过了解死锁条件来避免死锁。这是一个非常复杂的过程, 并不容易掌握。但是, 即使我们尝试, 也可以避免这种情况。有一些方法可以避免这种情况。我们无法完全消除它的可能性, 但可以减少。
  • 避免嵌套锁:这是死锁的主要原因。死锁主要发生在我们将锁授予多个线程时。如果我们已经给一个线程, 则避免给多个线程锁定。
  • 避免不必要的锁:我们应该只锁定那些必需的成员。不必要地锁定可能导致死锁。
  • 使用线程连接:当一个线程在等待另一线程完成时, 出现死锁状态。如果发生这种情况, 我们可以在你认为执行将花费最长时间的情况下使用Thread.join。
重要事项:
  • 如果线程正在互相等待完成, 则该条件称为死锁。
  • 死锁条件是一个复杂的条件, 仅在有多个线程的情况下才会发生。
  • 死锁条件可能会在运行时破坏我们的代码, 并可能破坏业务逻辑。
  • 我们应尽可能避免这种情况。

    推荐阅读