📜  Java多线程中的死锁

📅  最后修改于: 2022-05-13 01:55:11.646000             🧑  作者: Mango

Java多线程中的死锁

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

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.test1(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 的对象锁调用 test1 方法。
  3. t1 打印 test1-begin 和 t2 打印 test-2 begin 并且都等待 1 秒,因此如果其中任何一个线程都没有启动,那么两个线程都可以启动。
  4. t1 尝试获取 s2 的对象锁并调用 test2 方法,但由于它已经被 t2 获取,所以它一直等到它变得空闲。在获得 s2 的锁之前,它不会释放 s1 的锁。
  5. t2 也是如此。它试图获取 s1 的对象锁并调用方法 test1 但它已经被 t1 获取,所以它必须等到 t1 释放锁。 t2 在获得 s1 的锁定之前也不会释放 s2 的锁定。
  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 以及您认为执行所需的最长时间。

要点:

  • 如果线程正在等待彼此完成,则这种情况称为死锁。
  • 死锁条件是一个复杂的条件,仅在多线程的情况下才会出现。
  • 死锁条件会在运行时破坏我们的代码并破坏业务逻辑。
  • 我们应该尽可能避免这种情况。