Comparison of concurrency features of Java and C#

As promised in my previous post, here’s a quick comparison of concurrency management techniques in multithreaded application between Java and C#. First thing I’d like to get off my chest is to mention that concurrency facilities offered by the two languages are quite similar. This is not surprising given their history, of course. These facilities are structured exactly the same way and comprise 5 major parts.
First, there is a “default” locking/synchronization option that is safe and easy to use, but is not flexible and imposes a performance penalty, especially in truly concurrent with high level of contention. It is the first concurrency option that beginners learn and it is the best option for normal applications that do not require top performance. It is known as “synchronized” in Java and “lock” in C#.
Second, there are advanced locks that offer an ability to poll for a lock, limit on wait time, etc. These are advanced facilities and their downside is a need to be very careful to manually release each lock in a finally block. Their advantage is an opportunity to achieve several times better performance in highly concurrent applications with high level of contention. In Java, this facility is implemented by classes in java.util.concurrent.locks package, in C# it is System.Threading.Monitor
Third, there is a collection of pre-built primitives utilizing those advanced locks. They simplify coding for some typical locking applications.
Fourth, there is a supplementary mechanism for signalling between threads interested in the same resource.
Fifth, there is a lock-free, wait-free facility based on hardware-optimized (in Java, platform-dependent) Compare-and-swap pattern.
Here’s comparison of specific details of these facilities.

Facility Java C#
Simple “default” locks Implementation synchronized keyword lock keyword
Functionality Identical
Advanced locks Implementation java.util.concurrent.locks package, primarily ReentrantLock and ReentrantReadWriteLock System.Threading.Monitor class
Functionality Disadvantages: “Low observable”: not visible in thread dump, so are more difficult to troubleshoot and debug.Advantages: API is richer, supporting fair locks (guaranteeing lock is given to threads in the order it is requested) and multiple methods to examine queue of threads waiting for the lock). In real life fair locks are rarely used because of severe performance penalty. Advantages: As easy to debug as simple locks, because underlying implementation is exactly the same.Disadvantages: API more limited.
Pre-built primitives Implementation Various classes in java.util.concurrent package Various classes in System.Threading namespace
Functionality APIs are different, although some key concepts match.Comparison of specific primitives offered by Java and C# is a whole topic in itself, and one table cell can not possibly make it justice, so I will not even try to cover it here.
Signalling Implementation wait/notify/notifyAll methods of Object Wait/Pulse/PulseAll methods of Monitor
Functionality Effectively identical.
Lock-free, wait-free Implementation java.util.concurrent.atomic package System.Threading.Interlocked
Functionality Must use an instance of a class from java.util.concurrent.atomic package: you need to define an object as atomic to use this facility, which is quite limiting. In addition, Atomic classes are not directly related to regular non-atomics they represent (e.g. AtomicBoolean is not a java.lang.Boolean and AtomicInteger is not an java.lang.Integer, although it is a java.lang.Number). Any regular numeric type or reference can be used.
Advertisements

Can you see a java.util.concurrent.locks lock in a thread dump?

Before Java version 5, there was only one tool in the multithreading toolbox for protecting critical sections of code with a lock: synchronized methods and code sections. To date, synchronized remains the most reliable and simple method. However, it had a number of shortcomings, so Java SE version 5 introduced a few improvements, including more flexible locks, which are implemented as classes in java.util.locks.concurrent package. Naturally, increased flexibility came with a price. JUCL locks have a number of shortcomings, including “low observability”. Bear with me as I explain.
Regular locks and monitors that JVM creates to implement synchronized methods or sections appear in Java thread dumps, which is great for debugging, especially when JVM hangs. A permanent hang is frequently caused by a deadlock: a condition when thread A is waiting for a lock that is owned by Thread B while Thread B is waiting for a lock that is owned by Thread A. A thread dump clearly shows threads that own locks and threads that are waiting for them, which helps pinpoint the problem.
Here’s a couple of examples of thread dump fragments showing a thread holding a regular (synchronized) lock and another thread waiting to lock the same object:
A fragment of thread dump generated by IBM JVM, with Thread-6 holding a lock and Thread-7 waiting for it. Waiting thread is clearly identified.


1LKPOOLINFO Monitor pool info:
2LKPOOLTOTAL Current total number of monitors: 4
NULL
1LKMONPOOLDUMP Monitor Pool Dump (flat & inflated object-monitors):
2LKMONINUSE sys_mon_t:0x0086A804 infl_mon_t: 0x0086A83C:
3LKMONOBJECT LockTest$LockableResource@0x10139268/0x10139274: Flat locked by "Thread-6" (0x0250F600), entry count 1
3LKWAITERQ Waiting to enter:
3LKWAITER "Thread-7" (0x02652D00)

A fragment of thread dump generated by Sun JVM, with Thread-1 holding a lock on 0x2406eb00 and Thread-0 waiting to lock the same object ID.


"Thread-1" prio=6 tid=0x02be6000 nid=0x1b60 waiting for monitor entry [0x02f7f00
0]
java.lang.Thread.State: BLOCKED (on object monitor)
at LockTest$LockableResource.dosync(LockTest.java:32)
- waiting to lock <0x2406eb00> (a LockTest$LockableResource)
at LockTest$Worker.run(LockTest.java:42)

“Thread-0” prio=6 tid=0x02be5000 nid=0x1f9c waiting on condition [0x02f2f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at LockTest$LockableResource.dosync(LockTest.java:32)
locked <0x2406eb00> (a LockTest$LockableResource)
at LockTest$Worker.run(LockTest.java:42)

JUCL locks are different from the synchronized locks. They are just regular Java objects, so thread dump pays them no special attention, so these locks do not appear in thread dumps. This is unfortunate, but this behavior is by design. The best you can hope for is indirect evidence of a JUCL lock, and even that is not always available. You can not see a lock in locks/monitors section of a thread dump. Any help will have to come from analyzing call stacks. If you are using IBM JVM version 6, this is all you can go on. Version 6 of Sun JVM gives you one more hint: blocked thread will be in “WAITING (parking)” state. This is not a lot, but if you can not have a full loaf, half a loaf (actually, more like a crumb) is still better than nothing.
To illustrate the point, I rewrote test program to lock a Reentrant lock in one thread, put the thread to sleep and attempt to lock the same lock in another thread.
Using java.util.concurrent.lock.ReentrantLock instead of synchronized changed the look of a thread dump. In the monitors section of thread dump generated by IBM JVM, there was no mention of our JUCL locks. The number of monitors went down from 4 to 2, and the remaining 2 are system objects (JVM shutdown hook and garbage collector). Here’s the call stacks section of the thread dump, which does not show much. The most you will be able to see is that Thread-7 is waiting for a JUCL lock.


3XMTHREADINFO "Thread-6" J9VMThread:0x022F7700, j9thread_t:0x0086B4B4, java/lang/Thread:0x10128248, state:CW, prio=5
3XMTHREADINFO1 (native thread ID:0x1910, native priority:0x5, native policy:UNKNOWN)
3XMTHREADINFO3 Java callstack:
4XESTACKTRACE at java/lang/Thread.sleep(Native Method)
4XESTACKTRACE at java/lang/Thread.sleep(Thread.java:851)
4XESTACKTRACE at LockTest$LockableResource.trynew(LockTest.java:33)
4XESTACKTRACE at LockTest$Worker.run(LockTest.java:54)
3XMTHREADINFO3 Native callstack:
4XENATIVESTACK KiFastSystemCallRet+0x0 (0x7C90E514 [ntdll+0xe514])
4XENATIVESTACK WaitForSingleObject+0x12 (0x7C802542 [kernel32+0x2542])
4XENATIVESTACK j9thread_sleep_interruptable+0x101 (j9thread.c:1434, 0x7FFA12F1 [J9THR24+0x12f1])
4XENATIVESTACK jclCallThreadSleepInterruptable+0xe1 (threadhelp.c:214, 0x7FC97D31 [jclscar_24+0x37d31])
4XENATIVESTACK java_lang_Thread_sleep+0x61 (jlthr.asm:107, 0x7FC814E1 [jclscar_24+0x214e1])
4XENATIVESTACK javaProtectedThreadProc+0x7d (vmthread.c:1653, 0x7FF2C5CD [j9vm24+0x3c5cd])
4XENATIVESTACK j9sig_protect+0x41 (j9signal.c:144, 0x7FECBFA1 [J9PRT24+0xbfa1])
4XENATIVESTACK javaThreadProc+0x35 (vmthread.c:260, 0x7FF2CDF5 [j9vm24+0x3cdf5])
4XENATIVESTACK thread_wrapper+0xbf (j9thread.c:947, 0x7FFA3F2F [J9THR24+0x3f2f])
4XENATIVESTACK _endthread+0xaa (0x7C34940F [msvcr71+0x940f])
4XENATIVESTACK GetModuleFileNameA+0x1ba (0x7C80B729 [kernel32+0xb729])
NULL
3XMTHREADINFO "Thread-7" J9VMThread:0x023FC400, j9thread_t:0x0086B250, java/lang/Thread:0x101283E0, state:P, prio=5
3XMTHREADINFO1 (native thread ID:0x1B04, native priority:0x5, native policy:UNKNOWN)
3XMTHREADINFO3 Java callstack:
4XESTACKTRACE at sun/misc/Unsafe.park(Native Method)
4XESTACKTRACE at java/util/concurrent/locks/LockSupport.park(LockSupport.java:173)
4XESTACKTRACE at java/util/concurrent/locks/AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:759)
4XESTACKTRACE at java/util/concurrent/locks/AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:790)
4XESTACKTRACE at java/util/concurrent/locks/AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1126)
4XESTACKTRACE at java/util/concurrent/locks/ReentrantLock$NonfairSync.lock(ReentrantLock.java:198)
4XESTACKTRACE at java/util/concurrent/locks/ReentrantLock.lock(ReentrantLock.java:274)
4XESTACKTRACE at LockTest$LockableResource.trynew(LockTest.java:31)
4XESTACKTRACE at LockTest$Worker.run(LockTest.java:54)
3XMTHREADINFO3 Native callstack:

Thread dump generated in the same situation using Sun JVM gives a little more additional information: it specifies precisely what object Thread-1 is waiting to lock (0x2406ede0). Unfortunately, you have no way of knowing which thread owns this lock object.


"Thread-1" prio=6 tid=0x02be6000 nid=0x102c waiting on condition [0x02f7f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x2406ede0> (a java.util.concurrent.locks.Reentr
antLock$NonfairSync)

at java.util.concurrent.locks.LockSupport.park(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
errupt(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(U
nknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Unknown
Source)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(Unknown Sou
rce)
at java.util.concurrent.locks.ReentrantLock.lock(Unknown Source)
at LockTest$LockableResource.trynew(LockTest.java:31)
at LockTest$Worker.run(LockTest.java:54)

“Thread-0” prio=6 tid=0x02be5000 nid=0xcc8 waiting on condition [0x02f2f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at LockTest$LockableResource.trynew(LockTest.java:33)
at LockTest$Worker.run(LockTest.java:54)

To kick a dead horse, Microsoft’s .NET synchronization facilities are largely similar to Java, but their advanced locks are implemented using the same System.Threading.Monitor as simple locks and do not suffer from low observability during debugging. I will follow up on this post with a comparison of locking facilities for multithreading in Java and C#.

UPDATE 5/1/2011. As promised, I posted a comparison of synchronization facilities in Java and C#.