What Is “Locked Ownable Synchronizers” in Thread Dump? – 什么是Thread Dump中的 “锁定的可拥有的同步器”?

最后修改: 2022年 8月 24日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll look at the meaning of a thread’s locked ownable synchronizers. We’ll write a simple program that synchronizes using a Lock and see what this looks like in a thread dump.

在本教程中,我们将研究线程锁定的可拥有同步器的含义。我们将编写一个使用Lock进行同步的简单程序,并看看这在线程转储中是什么样子。

2. What Are Locked Ownable Synchronizers?

2.什么是锁定的可拥有的同步器?

Each thread potentially has a list of synchronizer objects. Entries in that list represent ownable synchronizers for which the thread has acquired the lock.

每个线程都可能有一个同步器对象的列表。该列表中的条目代表该线程已获得锁的可拥有的同步器

Instances of an AbstractOwnableSynchronizer class can be used as synchronizers. One of its most common subclasses is the Sync class which is a field of Lock interface implementations like ReentrantReadWriteLock.

AbstractOwnableSynchronizer类的实例可作为同步器使用。它最常见的子类之一是Sync类,它是Lock接口实现的一个字段,比如ReentrantReadWriteLock

When we call the ReentrantReadWriteLock.lock() method, internally the code delegates this to the Sync.lock() method. Once we acquire a lock, the Lock object is added to the locked ownable synchronizers list of the thread.

当我们调用ReentrantReadWriteLock.lock()方法时,在内部,代码将此委托给Sync.lock()方法。一旦我们获得一个锁,Lock对象就会被添加到线程的可锁定的同步器列表中

We can view this list in a typical thread dump:

我们可以在一个典型的线程转储中查看这个列表。

"Thread-0" #1 prio=5 os_prio=0 tid=0x000002411a452800 nid=0x1c18 waiting on condition [0x00000051a2bff000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.baeldung.ownablesynchronizers.Application.main(Application.java:25)

   Locked ownable synchronizers:
        - <0x000000076e185e68> (a java.util.concurrent.locks.ReentrantReadWriteLock$FairSync)

Depending on the tool we use to generate it, we might have to provide a specific option. Using jstack, for example, we run the following command:

根据我们用来生成它的工具,我们可能必须提供一个特定的选项。例如,使用jstack,我们运行以下命令。

jstack -l <pid>

With the -l option we tell jstack to print additional information about locks.

通过-l选项,我们告诉jstack打印关于锁的额外信息。

3. How Locked Ownable Synchronizers Help

3.锁定的可拥有的同步器如何帮助

The ownable synchronizers list helps us identify possible application deadlocks. For example, we can see in the thread dump if a different thread named Thread-1 is waiting to acquire a lock on the same Lock object:

可拥有的同步器列表有助于我们识别可能的应用死锁。例如,我们可以在线程转储中看到,如果一个名为Thread-1的不同线程正在等待获取同一个Lock对象的锁。

"Thread-1" #12 prio=5 os_prio=0 tid=0x00000241374d7000 nid=0x4da4 waiting on condition [0x00000051a42fe000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076e185e68> (a java.util.concurrent.locks.ReentrantReadWriteLock$FairSync)

Thread Thread-1 is in state WAITING. Specifically, it waits to acquire a lock on the object with id <0x000000076e185e68>. However, the same object is in the locked ownable synchronizers list of thread Thread-0. We now know that until thread Thread-0 releases its own lock, thread Thread-1 cannot continue.

线程Thread-1处于WAITING状态。具体来说,它在等待获取一个ID为<0x000000076e185e68>的对象的锁。然而,同一个对象在线程Thread-0的锁定的可拥有同步器列表中。我们现在知道,直到线程Thread-0释放它自己的锁,线程Thread-1不能继续

If at the same time the same scenario happens in reverse, i.e., Thread-1 has acquired a lock that Thread-0 waits for, we’ve created a deadlock.

如果在同一时间,同样的情况反向发生,即Thread-1获得了Thread-0等待的锁,我们就创造了一个死锁。

4. Deadlock Diagnosis Example

4.死锁诊断实例

Let’s look at some simple code that illustrates all of the above. We’ll create a deadlocking scenario with two threads and two ReentrantLock objects:

让我们看看一些简单的代码来说明上述所有的情况。我们将创建一个有两个线程和两个ReentrantLock对象的死锁场景。

public class Application {

    public static void main(String[] args) throws Exception {
        ReentrantLock firstLock = new ReentrantLock(true);
        ReentrantLock secondLock = new ReentrantLock(true);
        Thread first = createThread("Thread-0", firstLock, secondLock);
        Thread second = createThread("Thread-1", secondLock, firstLock);

        first.start();
        second.start();

        first.join();
        second.join();
    }
}

Our main() method creates two ReentrantLock objects. The first thread, Thread-0, uses firstLock as its primary lock and secondLock as its secondary lock.

我们的main()方法创建了两个ReentrantLock对象。第一个线程,Thread-0,使用firstLock作为其主锁,secondLock作为其辅助锁。

We’ll do the same thing in reverse for Thread-1. Specifically, our goal is to generate a deadlock by having each thread acquire its primary lock and hang when trying to acquire its secondary lock.

我们将对Thread-1做同样的事情。具体来说,我们的目标是通过让每个线程获得其主锁,并在试图获得其副锁时挂起,从而产生一个死锁。

The createThread() method generates each of our threads with their respective locks:

createThread()方法为我们的每个线程生成了各自的锁。

public static Thread createThread(String threadName, ReentrantLock primaryLock, ReentrantLock secondaryLock) {
    return new Thread(() -> {
        primaryLock.lock();

        synchronized (Application.class) {
            Application.class.notify();
            if (!secondaryLock.isLocked()) {
                Application.class.wait();
            }
        }

        secondaryLock.lock();

        System.out.println(threadName + ": Finished");
        primaryLock.unlock();
        secondaryLock.unlock();
    });
}

To make sure that each thread will have locked its primaryLock before the other thread tries to, we wait for it using isLocked() inside the synchronized block.

为了确保每个线程在其他线程试图锁定其primaryLock之前,我们在synchronized块内使用isLocked()等待它。

Running this code will hang and never print the finished console output. If we run jstack, we’ll see the following:

运行这段代码会挂起,而且永远不会打印出完成的控制台输出。如果我们运行jstack,我们会看到下面的情况。

"Thread-0" #12 prio=5 os_prio=0 tid=0x0000027e1e31e800 nid=0x7d0 waiting on condition [0x000000a29acfe000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076e182558>

   Locked ownable synchronizers:
        - <0x000000076e182528>


"Thread-1" #13 prio=5 os_prio=0 tid=0x0000027e1e3ba000 nid=0x650 waiting on condition [0x000000a29adfe000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076e182528>

   Locked ownable synchronizers:
        - <0x000000076e182558>

We can see that Thread-0 is parked to wait for 0x000000076e182558 while Thread-1 for 0x000000076e182528. At the same time, we can find these handles in the locked ownable synchronizers of their respective threads. Basically, this means that we can see which locks our threads are waiting for and which thread owns those locks. This helps us troubleshoot concurrency issues including deadlocks.

我们可以看到,Thread-0被停在那里等待0x000000076e182558,而Thread-1等待0x000000076e182528。同时,我们可以在他们各自线程的锁定的可拥有的同步器中找到这些句柄。基本上,这意味着我们可以看到我们的线程正在等待哪些锁,哪个线程拥有这些锁。这有助于我们对包括死锁在内的并发问题进行排查。

An important thing to note is that if instead of a ReentrantLock we used a ReentrantReadWriteLock.ReadLock as a synchronizer, we wouldn’t get the same information in the thread dump. Only ReentrantReadWriteLock.WriteLock shows up in the synchronizers list.

需要注意的是,如果我们不使用ReentrantLock,而是使用ReentrantReadWriteLock.ReadLock作为同步器,我们就不会在线程转储中得到同样的信息。只有ReentrantReadWriteLock.WriteLock在同步器列表中显示出来

5. Conclusion

5.总结

In this article, we discussed the meaning of the locked ownable synchronizers list that appears in a thread dump, how we can use it to troubleshoot concurrency issues, and also saw an example scenario.

在这篇文章中,我们讨论了线程转储中出现的锁定的自有同步器列表的含义,我们如何使用它来排除并发问题,还看到了一个示例场景。

As always, the source code for this article is available over on GitHub.

一如既往,本文的源代码可在GitHub上获得over