0
votes

I'm looking at a jstack log and this is what i see:

"com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#2" #250 daemon prio=5 os_prio=0 tid=0x00007f9de0016000 nid=0x7e54 runnable [0x00007f9d6495a000] java.lang.Thread.State: RUNNABLE at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:534) - locked <0x00000006fa818a38> (a com.mchange.v2.async.ThreadPoolAsynchronousRunner)

"com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#1" #249 daemon prio=5 os_prio=0 tid=0x00007f9de000c000 nid=0x7e53 waiting for monitor entry [0x00007f9d649db000] java.lang.Thread.State: BLOCKED (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000006fa818a38> (a com.mchange.v2.async.ThreadPoolAsynchronousRunner) at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:534) - locked <0x00000006fa818a38> (a com.mchange.v2.async.ThreadPoolAsynchronousRunner)

"com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#0" #248 daemon prio=5 os_prio=0 tid=0x00007f9de001a000 nid=0x7e52 waiting for monitor entry [0x00007f9d64a5c000] java.lang.Thread.State: BLOCKED (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000006fa818a38> (a com.mchange.v2.async.ThreadPoolAsynchronousRunner) at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:534) - locked <0x00000006fa818a38> (a com.mchange.v2.async.ThreadPoolAsynchronousRunner)

So, in this log, each of these three threads has managed to get the same lock and the bottom two threads are actually blocked waiting for the same lock.

Can someone please explain to me what this stack log means?

3

3 Answers

2
votes

The last two threads are waiting to be notified by using the instance of ThreadPoolAsynchronousRunner as monitor, so the source of that will look something like this:

synchronized(asyncRunner) {
    // ...
    asyncRunner.wait();
    // ...
}

As soon as you call wait, the synchronization on asyncRunner is "released", i.e. other parts of the application can enter a block that is synchronized on that instance. In your particular case it seems that this has happened and the first thread's wait-call returned and it's currently processing some data that comes from it. You still see multiple locked-lines in the thread-dump to show you that the code is currently within a synchronized-block but as said, the "lock" is released when calling wait.

The technique you see here as a thread-dump is quite common before the concurrent-package was added to the JDK to avoid costly thread-creations. And your thread-dump looks like this kind of implementation. Here is a simple implementation how it might look like "under the hood":

// class ThreadPoolAsynchronousRunner
private Deque<AsyncMessage> queue;

public synchronized void addAsyncMessage(AsyncMessage msg) {
    queue.add(msg);
    notifyAll();
}

public void start() {
    for (int i = 0; i < 4; i++) {
        PoolThread pt = new PoolThread(this);
        pt.start();
    }
}

The ThreadPoolAsynchronousRunner`` starts PoolThreads and does a notifyAll if a new message to be processed is added.

// PoolThread

public PoolThread(ThreadPoolAsynchronousRunner parent) {
    this.parent = parent;
}

public void run() {
    try {
        while (true) {
            AsyncMessage  msg = null;
            synchronized(parent) {
                parent.wait();
                if (!parent.queue.isEmpty()) {
                    msg = queue.removeFirst();
                }
            }
            if (msg != null) {
                processMsg(msg);
            }
        }
    }
    catch(InterruptedException ie) {
        // exit
    }
}

notifyAll will lead all wait-methods of all threads to return, so you have to check if the queue in the parent still contains data (sometimes wait returns even without a notification taken place, so you need this check even if not using notifyAll). If that's the case you start the processing method. You should do that outside the synchronized-block otherwise your async-processing class only processes one message at the time (unless, that's what you want - but then why run multiple PoolThread-instances?)

0
votes

What I can see and understand is that

Thread-#2 is in Runnable state and has acquired a lock on an Object

"com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#2" java.lang.Thread.State: RUNNABLE

Thread-#1 and Thread-#0 are waiting for that Object lock to be released and hence blocked right now.

"com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#1" "com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#0" java.lang.Thread.State: BLOCKED (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000006fa818a38>

0
votes

Only Thread-#2 has managed to get Object lock successfully and it is in RUNNABLE state. Other 2 threads, i.e., Thread-#0 and Thread-#1 are waiting for that lock to be released by Thread-#2. As long as Thread-#2 holds the lock, Thread-#0 and Thread-#1 will remain locked and will be in a state BLOCKED.

If you have access to source code, You can review that code just to ensure if locking and unlocking is done in proper order and lock has been been held only for part of code where it is necessary. Remember these 2 threads are not in WAIT state but in BLOCKED state which is a step after WAIT state and just a step before getting in to RUNNABLE state as soon as lock is available.

There is no problem observed in this log snippet. This is not a deadlock scenario yet.