个性化阅读
专注于IT技术分析

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

synchronized关键字用于使类或方法具有线程安全性, 这意味着只有一个线程可以拥有同步方法的锁并可以使用它, 其他线程必须等到锁释放后才能由他们中的任何一个获取。

如果我们的程序在多线程环境中运行(其中两个或多个线程同时执行),那么使用它是很重要的。但有时也会导致死锁。下面是死锁条件的一个简单示例。

Java多线程中的死锁1

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 condition

JNI global refs: 15, weak refs: 0


Found 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个死锁。当你尝试使用计算机时, 可能会出现相同的消息。

避免死锁状态

我们可以通过了解死锁条件来避免死锁。这是一个非常复杂的过程, 并不容易掌握。但是, 即使我们尝试, 也可以避免这种情况。有一些方法可以避免这种情况。我们无法完全消除它的可能性, 但可以减少。

  • 避免嵌套锁:这是死锁的主要原因。死锁主要发生在我们将锁授予多个线程时。如果我们已经给一个线程, 则避免给多个线程锁定。
  • 避免不必要的锁:我们应该只锁定那些必需的成员。不必要地锁定可能导致死锁。
  • 使用线程连接:当一个线程在等待另一线程完成时, 出现死锁状态。如果发生这种情况, 我们可以在你认为执行将花费最长时间的情况下使用Thread.join。

重要事项:

  • 如果线程正在互相等待完成, 则该条件称为死锁。
  • 死锁条件是一个复杂的条件, 仅在有多个线程的情况下才会发生。
  • 死锁条件可能会在运行时破坏我们的代码, 并可能破坏业务逻辑。
  • 我们应尽可能避免这种情况。
赞(0)
未经允许不得转载:srcmini » Java多线程中的死锁详细介绍

评论 抢沙发

评论前必须登录!