Authors Top

If you have a few years of experience in the Java ecosystem, and you’d like to share that with the community, have a look at our Contribution Guidelines.

Generic Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

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.

2. What Are Locked Ownable Synchronizers?

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.

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.

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 -l <pid>

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

3. How Locked Ownable Synchronizers Help

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" #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.

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.

4. Deadlock Diagnosis Example

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:

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.

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.

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

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.

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

"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.

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.

5. Conclusion

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.

Generic bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
guest
0 Comments
Inline Feedbacks
View all comments