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