views:

116

answers:

4

I've noticed something very strange yesterday. It seems that two threads are entering two synchronized blocks locking on the same object at the same time.

The class (MyClass) containing the relevant code looks similar to this:

private static int[]    myLock  = new int[0];

protected static int methodA(final long handle, final byte[] sort) {
    synchronized (myLock) {
        return xsMethodA(handle, sort);
    }
}

protected static int methodB(final long handle) {
    synchronized (myLock) {
        return xsMethodB(handle);
    }
}

I created a thread dump of my application running the above class and was very surprised as I saw this:

"http-8080-136" daemon prio=10 tid=0x00000000447df000 nid=0x70ed waiting for monitor entry [0x00007fd862aea000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.MyClass.methodA(MyClass.java:750)
    - locked <0x00007fd8a6b8c790> (a [I)
    at com.SomeOtherClass.otherMethod(SomeOtherClass.java:226)
    ...

"http-8080-111" daemon prio=10 tid=0x00007fd87d1a0000 nid=0x70c8 waiting for monitor entry [0x00007fd86e15f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.MyClass.methodB(MyClass.java:991)
    - locked <0x00007fd8a6b8c790> (a [I)
    at com.SomeOtherClass.yetAnotherMethod(SomeOtherClass.java:3231)
    ...

(I changed the class and method names for the case of simplicity, so don't get confused by the silly names.)

It seems that thread http-8080-136 and http-8080-111 have both acquired the lock on myLock. It is the same object as the object address is the same: 0x00007fd8a6b8c790. The Java Runtime Specification says this about the synchronized keyword:

A synchronized statement acquires a mutual-exclusion lock (§17.1) on behalf of the executing thread, executes a block, then releases the lock. While the executing thread owns the lock, no other thread may acquire the lock. [The Java Language Specification, 14.19]

So how is this even possible?

There are another 44 threads in the thread dump "waiting" for the lock. This is how it looks like if a thread is waiting:

"http-8080-146" daemon prio=10 tid=0x00007fd786dab000 nid=0x184b waiting for monitor entry [0x00007fd8393b6000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.MyClass.methodC(MyClass.java:750)
    - waiting to lock <0x00007fd8a6b8c790> (a [I)
    at com.SomeOtherClass.yetAnoterMethod2(SomeOtherClass.java:226)
+1  A: 

How was the thread dump taken? If the threads were not paused the lock ownership could have changed between dumping one thread and the next.

fd
By sending the QUIT signal to the process. I don't know how the Sun VM acts during the thread dump. But I'd assume that the process is halted. Otherwise you would get an inconsistent thread dump.
Eduard Wirch
I know for the IBM JVM this is not necessarily true, not sure about SUN, however, definitely something to bear in mind.
fd
This site claims that all threads are paused: http://expertodev.wordpress.com/2009/05/30/how-to-take-java-thread-dump/
Eduard Wirch
A: 

I think the relevant information is: "waiting for monitor entry", which is the same for both threads. Since both threads (in the thread dump) are marked deamon threads, I guess there must also be a main-thread running at the same time. Is it possible that the main-thread is the current monitor owner who blocks the other two threads?

Javaguru
No, the main thread does nothing. Even if the main thread would hold the lock, the specification does not distinguish between main and deamon threads. Only one thread is allowed to own a lock.
Eduard Wirch
I agree, only one thread is allowed to gain the lock and to enter the critical section (according to specification). Both deamon-threads are *waiting* for the lock, as stated in the thread dump. Are you sure, there is no other thread currently holding the lock?
Javaguru
The two threads http-8080-136 and http-8080-111 are holding the lock. There are another 44 threads in the thread dump "waiting" for the lock.
Eduard Wirch
What exactly do you mean by "holding the lock"? Threads 111 and 136 are BLOCKED because they are *trying to acquire* the lock which is currently hold by another thread. Am I wrong, or are these 44 threads all blocked, trying to acquire the lock? Or did anyone of the 44 threads finish its execution?
Javaguru
The thread dump for http-8080-136 and http-8080-111 says: locked <0x00007fd8a6b8c790>. So they already acquired the lock. The thread dump for the 44 remaining threads says: waiting to lock <0x00007fd8a6b8c790>.
Eduard Wirch
A: 

They haven't acquired lock, otherwise you would see xsMethodA or xsMethodB in a stacktrace.

Ha
So why is there a difference between threads http-8080-111 and http-8080-146?
Eduard Wirch
And: ThreadDumpAnalyzer lists both threads (http-8080-136, http-8080-111) as "locked by".
Eduard Wirch
I mean that title "synchronized doesn't block" is wrong. May be you mean "synchronized block blocks when no one holding the lock"?
Ha
This is not true. Two threads **are** holding the lock. 44 threads are waiting for the lock.
Eduard Wirch
You think that they hold the lock but they do not. Otherwise xsMethodA and xsMethodB would be in a stacktrace almost certainly.
Ha
+1  A: 

I've asked the same question on the hotspot-dev mailing list and received a very goot answer from Christopher Phillips:


Hi Eduard

I think its the thread dump that is misleading.

If you really think that the 2 are in the lock simultaneously you should probably get a gcore (which is externally consistent).

The state you see "waiting for monitor entry" is actually MONITOR_WAIT which can represent the following code before actual acquisition of a hot lock : (also see OSThreadContendState in osThread.hpp) called from: src/share/vm/runtime/synchronizer.cpp

3413      OSThreadContendState osts(Self->osthread());
3414      ThreadBlockInVM tbivm(jt);
3415
3416      Self->set_current_pending_monitor(this);
3417
3418      // TODO-FIXME: change the following for(;;) loop to straight-line code.
3419      for (;;) {
3420        jt->set_suspend_equivalent();
3421        // cleared by handle_special_suspend_equivalent_condition()
3422        // or java_suspend_self()
3423
3424        EnterI (THREAD) ;
3425
3426        if (!ExitSuspendEquivalent(jt)) break ;
3427
3428        //
3429        // We have acquired the contended monitor, but while we were
3430        // waiting another thread suspended us. We don't want to enter
3431        // the monitor while suspended because that would surprise the
3432        // thread that suspended us.

Chris

Eduard Wirch