Upgrade to Pro — share decks privately, control downloads, hide ads and more …

5 Things You Didn’t Know About Synchronization in Java and Scala

Takipi
August 15, 2013

5 Things You Didn’t Know About Synchronization in Java and Scala

Practically all server applications require synchronization between multiple threads. Most of the work is done for us at the framework level, such as by our web server, DB client or messaging framework. Java and Scala provide a multitude of components to write solid multi-threaded applications. These include object pools, concurrent collections, advanced locks, execution contexts etc.. To better understand these, let’s explore the most synchronization idiom – the Object lock.

Takipi

August 15, 2013
Tweet

More Decks by Takipi

Other Decks in Programming

Transcript

  1. Object Locking The most fundamental multi-threading idiom in Java, supported

    by the compiler and VM. Fact #1 • synchronized methods are compiled with a special attribute in the .class file. • synchronized blocks are implemented using 2 bytecode instructions - MonitorEnter and MonitorExit. This differs from other locking mechanisms found java.util.concurrent , implemented (in HotSpot) using Java code and native calls made through sun.misc.Unsafe. The monitor instructions operate on the object specified by the synchronized code block. Synchronized methods lock the this variable. Static methods lock the class object. takipi.com
  2. Possible Complications The automatic lock selection in synchronized methods can

    sometime cause unwanted behavior. This includes creating dependencies between different synchronized methods for the same object - they share the same lock. One example is declaring synchronized methods in a base class (might even 3rd party) and adding new ones to a derived class. This creates implicit synchronization dependencies across the hierarchy, which can lead to throughput or even deadlocking issues. To avoid this, use a private object as a lock to prevent accidental sharing or escapement of locks. takipi.com
  3. Monitor Instructions There are two bytecode synchronization instructions – MonitorEnter

    for acquiring and MonitorExit for releasing a lock. This is unusual as most instructions are independent of each other, usually “communicating” with one another using values previously placed on the thread’s operand stack. Monitor instructions pop the target object to lock from the stack, placed there by either a variable or field dereference, or a method return value. Fact #2 - What happens if one of the instructions is called without a respective call to the other? takipi.com
  4. Monitor Instructions (2) The Java compiler will not produce code

    that calls MonitorExit without calling MonitorEnter. Even so, from the JVM perspective such code is totally valid. In such as case, a MonitorExit instruction with throw an IllegalMonitorStateException. If a lock is acquired via MonitorEnter, but isn’t released via MonitorExit, other threads trying to obtain it will block indefinitely. Since the lock is reentrant, the owning thread may continue to execute even if it were to reach (or reenter) the same lock again. So, how is this prevented? takipi.com
  5. Synchronized and The Compiler The compiler generates matching enter and

    exit instructions so that once execution has entered into a synchronized block it must pass through a matching MonitorExit instruction for the same object. Fact #3 – what happens if an exception is thrown within the critical section? To prevent the stack from unwinding without going through the MonitorExit , the compiler adds an implicit catch clause. To understand, Let’s analyze the bytecode for a synchronized block - public void hello() { synchronized (this) { System.out.println("I'm alone"); } } takipi.com
  6. This is the bytecode for hello() - aload_0 // load

    this into the operand stack Dup // load again astore_1 // backup this into an implicit variable at register 1 Monitorenter // pop the value of this into the monitor //the critical section code – getstatic java/lang/System/out Ljava/io/PrintStream; ldc "I'm alone" invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V aload_1 //load the backup of this monitorexit //pop up the var and exit the monitor goto 14 // completed - jump to the end // the added catch clause - we got here if an exception was thrown - aload_1 // load the backup var. monitorexit //exit the monitor athrow // rethrow the exception object return takipi.com
  7. How Locks Are Implemented Let’s look at how locking is

    implemented in HotSpot SE 7 (this is VM specific). Locking is an intrinsic capability each Java objects has, like having a default hashcode or a reference to its defining class. #Fact 4 - This data is encoded into each object’s header (also known as the object’s mark). For 64 bits - This data contains the object’s locking state (i.e. locked / unlocked) and a reference to the thread which currently owns the locks. takipi.com
  8. The Locking Algorithm When the JVM attempts to acquire a

    lock on an object it goes through a series of steps from optimistic to pessimistic. #Fact 5 - A lock is acquired by a thread if it succeeds in establishing itself as the object lock’s owner. This is determined by whether the thread is able to install a pointer to itself (the VM JavaThread object) in the object’s header. A first attempt to do this is done using a compare-and-exchange (CAS) operation. This is very efficient, as it can usually translate into a direct CPU instruction (e.g cmpxchg). CAS operations along with an OS specific thread parking routine serve as the building blocks for the object synchronization idiom. takipi.com
  9. The Locking Algorithm (2) Step 1. If the CAS succeeds

    then a lock on the object is obtained for the thread and execution continues. Step 2. If the CAS fails the JVM makes an additional attempt to acquire the lock using a SpinLock approach. The thread parks put it to sleep between waits, after which it retries to install itself as the lock’s owner. Step 3. If initial attempts fail (signaling a fairly higher level of contention) the thread will change its state to blocked and enqueue itself with other blocked threads. GC. After each wakeup the thread will attempt to acquire the lock. It also checks for changes in the JVM state, such as the onset of a “stop the world” GC in which case it will suspend until the GC completes. takipi.com
  10. The Locking Algorithm (2) When exiting the critical section via

    MonitorExit, the owner thread will try to see if it can wake any of the parked threads which may be waiting for the lock. This process is known as choosing an “heir”. This increases liveliness, to prevent a scenario where threads remain parked while the lock has already been released - a situation known as stranding. takipi.com
  11. Additional Reading - 1. To dive even deeper into how

    the JVM implements locking, code and comments are available here. 2. A full guide on all the different Java synchronization APIs and techniques can be found here. 3. An here’s a thorough guide on concurrency in Scala. takipi.com