Synchronization

  • Forces one by one access to method or data
  • Prevents data access corruption
  • Synchronization is costly to perform
  • Some data structures are synchronized internally
  • Using the synchronized keyword with a constructor is a syntax error
  • example:
    • System.out.println() — Multiple threads will fight for the System.out object and hence can cause throughput issues
    • synchronized: Vector, StringBuffer
    • unsynchronized: ArrayList, StringBuilder

Locking

  • Required for synchronization
  • Threads that need to access the object must acquire the lock of the object

Intrinsic Lock or Monitor Lock

  • aka Monitor/Monitor Object
  • Java provides built-in locking mechanism for enforcing atomicity:
    • synchronized block
    • synchronized method
  • Every Object in Java can implicitly act as a lock for purpose of synchronization
    • Primitives cannot be used as monitors
  • Intrinsic locks in Java acts as mutexes (mutual exclusion locks) which means at most one thread can own the lock
  • The execution happens atomically in synchronized blocks
  • The only way to acquire an intrinsic lock is to enter a synchronized block or method guarded by that lock
  • Intrinsic lock is automatically
    • acquired before entering a synchronized block
    • released when control exits the synchronized block normally or by throwing exception
  • Methods:

Synchronized Block

  • Block of code where a thread can enter one at a time
  • Synchronized block has two parts:
    • a reference to an object that will serve as lock
    • block of code to be guarded by that lock
  • Only one thread can execute inside a Java code block synchronized on the same monitor object.
  • In the following example spoon variable is a monitor object.
synchronized (spoon) {
	//code
}

Synchronized Methods

  • If a method is marked synchronized by adding a keyword in its method definition, then the function can only be entered by one thread at a time.
  • It is shorthand for a synchronized block that spans an entire method body, and whose lock is the object on which the method is being invoked
    • in case of static method, Class object is used
public class Test {
    synchronized void instanceMethod() {
        // code
    }
 
    // same as
    void instanceMethod() {
        synchronized(this) {
            // code
        }
    }
 
    // ------------------------------------------------
 
    static synchronized void staticMethod() {
        // code
    }
 
    // same as
    static void staticMethod() {
        synchronized(Test.class) {
            // code
        }
    }
}

Java Monitor Pattern

  • Using this or Class as lock can be problematic because it can be accessed by outside world and someone can acquire lock causing issues
  • We can have a private lock and use it as follows:
public class Test {
    private static final myLock = new Object();
 
    void method() {
        synchronized(myLock) {
            // code
        }
    }
}

Reentrant Synchronization

  • Allowing a thread to acquire the same lock more than once enables reentrant synchronization
  • This describes a situation where synchronized code, directly or indirectly, invokes a method that also contains synchronized code, and both sets of code use the same lock
  • Without reentrant synchronization, synchronized code would have to take many additional precautions to avoid having a thread cause itself to block.
  • Intrinsic locks are reentrant
  • Reentrancy means that locks are acquired on a per-thread rather than per-invocation basis
    • pthreads mutexes works on per-invocation basis and are not reentrant