Recent Tutorials and Articles
    Threads Synchronization in Java
    Published on: 2018-05-25 16:05:32

    This tutorial talks about the problems introduced due to concurrent access by multiple threads and then finally about the basic ways to get over these problems.

    This tutorial assumes that you have got the basic understanding of threads in java. It would be useful if you could read this first if you are new to threads.

    Potential problems in Multi-threaded application

    As we know that threads share the same execution environment consisting memory(heap not stack as each thread has got its own stack), open file handles and other resources. Since instance/class fields (not local fields as these are created on stack) are created on heap, all the threads can access it at any point of time and this is what exposes the application to following potential issues-

    • Thread Interference:

      Let's consider the following example class which generates the id by incrementing the currentId field-

      IDGenerator.java
      package com.sts.allprogtutorials;
      
      /**
       * @author Sain Technology Solutions
       */
      public class IDGenerator {
      	private int currentId;
      	
      	public int getNewId() {
      		return ++currentId;
      	}
      	
      	public int getCurrentId() {
      		return currentId;
      	}
      }
      
      

      Now if we call getNewId method of above class sequentially within a single thread, the currentId will be increased by 2. But let's talk about the operation ++currentId. It might seem like one operation but it is actually made of following 3 steps -

      1. Read value of field ++currentId from memory
      2. Increment the field ++currentId value by 1
      3. Update value of field ++currentId in the memory

      What would happen if those 2 calls to getNewId method (assuming currentId is 0 at that time) are made from 2 different threads at the same time? Following is one of the possibilities showing thread interference problem -

      1. Thread-1 reads the value of field ++currentId from memory as 0
      2. Thread-2 reads the value of field ++currentId from memory as 0
      3. Thread-1 increments the value of field ++currentId
      4. Thread-1 updates the incremented value of field ++currentId in memory as 1
      5. Thread-2 increments the value of field ++currentId
      6. Thread-2 updates the incremented value of field ++currentId in memory as 1

      As we can see that due to interference of other thread, work of one thread is totally overwritten by another.

    • Memory Inconsistency Problems:

      This problem is similar to thread interference but only difference is that it talks more about the inconsistent views the various threads have of same data. These inconsistent views have their origins in java memory model as java tends to either reorder/optimise the code or cache the shared variables to reduce the frequent reads/writes from heap and thereby increasing the performance but without any impact on the program output if program was running in a single thread. This re-ordering and caching however creates memory inconsistency problems in case of multi-threaded applications.

      Let's consider our IDGenerator class. Suppose we are using this class into multi-threaded application where thread-1 calls getNewId which results into currentId being increments and just after that thread-2 calls getCurrentId method and prints the currentId. In this case, we would expect it to print the incremented value but in some case, we can still get the un-incremented value as there is no happens-before relationship (thread-2 doesn't necessarily sees the changes done by thread-1) between these 2 operations in 2 different threads.

    Threads Synchronization

    In last section, we saw that there could be thread interference problems (if a thread interfered into another thread processing and thereby making the output inconsistent) and memory inconsistency problems (result of JVM code re-ordering/optimisation and data caching i.e. not guaranteeing happens-before relationship in multi-threaded environment). So if we want to solve these problems, we need to do following -

    1. Synchronising threads execution
    2. Establishing happens-before relationship in multi-threaded environment

    Synchronized methods & blocks:

    Both of above conditions can be fulfilled by using synchronized (it's a keyword, notice 's') methods and blocks. These take care of above condition by using monitor lock. monitor is something which all the objects implicitly have and a thread acquires the monitor lock for an object using synchronized methods and blocks. Only one thread can acquire monitor lock on an object and hence only thread with monitor lock can execute synchronized method and blocks while other threads would be waiting for the lock to be available again. It surely creates contention as threads might keep waiting and simply timeout. There are more sophisticated ways to do this which we will cover in a separate tutorial.

    Regarding happens-before relationship, here are the rules in case of synchronized methods and blocks -

    1. All actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor i.e. all writes to the shared variables will be visible to statements in synchronized methods and blocks
    2. Since only one thread can access synchronized code, every statement happens-before the next statement
    3. An unlock of a monitor happens-before every subsequent lock of that same monitor i.e. all the writes by a thread in synchronized block will be visible to another thread acquiring the lock on same monitor

    As we can see that synchronized blocks/methods also take care of happens before thereby resolving memory inconsistency problem. Let's see the syntax for synchronized methods and blocks -

    Synchronized Methods & blocks Syntax
    //Synchronized method
    <access-modifier> <static> <final> synchronized <return-type> <method-name>(<input-parameters>) throws <throwable> {
    }
    
    //Synchronized block
    synchronized(<object to acquire lock on, for current object, use this>) {
    }
    
    

    So we just need to add synchronized keywords in starting of a method to make it synchronized. For instance methods, only one thread can execute the synchronized instance methods of an instance while unsynchronized and static synchronized methods could still be accessed by multiple threads.

    In case of static methods, lock is taken on Class objects method and only one thread can access static methods while unsynchronized and synchronized instance methods could still be accessed by multiple threads.

    This basically stresses on one thing that synchronization always happens on an object. If there is a block synchronizing on Object object1 in method-1 and another block synchronizing on Object object2 in method-2. Both of these blocks can be executed simultaneously by 2 different threads as they are locking on different objects. However it is not to be confused with the fact that a block will still be executed by just one thread. So it's more about relation of one synchronize block to another.

    This is how our IDGenerator class would look like if we made all of its methods synchronized (Please note that it is necessary to make getter method synchronized to establish happen-before relationship for the writes to currentId variable) -

    IDGenerator.java (Synchronized version)
    package com.sts.allprogtutorials;
    
    /**
     * @author Sain Technology Solutions
     */
    public class IDGenerator {
    	private int currentId;
    	
    	public synchronized int getNewId() {
    		return ++currentId;
    	}
    	
    	public synchronized int getCurrentId() {
    		return currentId;
    	}
    }
    
    

    Re-entrant Monitor Locking: This is an important feature of monitor lock which means that a thread that owns monitor lock on an object can again acquire the monitor lock on same object as otherwise the developers have to code with caution and always make sure that there is no calls to synchronized methods (or containing synchronized blocks) from synchronized methods/blocks because that could result the thread to block for ever.

    let's consider the following scenario in which getNewId synchronized method is calling another synchronized method getCurrentId. If the lock was not re-entrant, calll to method getNewId() will block for ever resulting into deadlock)

    IDGenerator.java (Re-entrant Synchronized version)
    package com.sts.allprogtutorials;
    
    /**
     * @author Sain Technology Solutions
     */
    public class IDGenerator {
    	private int currentId;
    	
    	public synchronized int getNewId() {
    		++currentId;
    		return getCurrentId();
    	}
    	
    	public synchronized int getCurrentId() {
    		return currentId;
    	}
    }
    
    

    Volatile fields:

    Volatile fields are one more way to ensure write operations happen-before the read operations to the same field as JVM doesn't do reordering and caching (before Java5, JVM was allowed to re-order statements involving volatile field and this is why it was not quite useful at that time) for volatile fields but it is important to note that this does NOT solve the problem of thread interference as it does not synchronize the order of threads execution.

    A field can be made volatile by putting volatile keywords next to access identifier(if any). Here is how you can make currentId field volatile-

    private volatile int currentId;
    
    

    N.b. Only instance/static variables can be volatile. Since local variables are not shared by threads, it doesn't make sense for them to be volatile. Similarly methods can't be volatile.

    Immutable Objects:

    All of the thread related issues occur due to concurrent manipulation of fields by multiple threads. What if the field object (data) couldn't be changed once created (i.e. immutable object)? In this case, we wouldn't have any of the above discussed problems. Therefore if it is possible, developers should keep the fields immutable.

    Thank you for reading through the tutorial. In case of any feedback/questions/concerns, you can communicate same to us through your comments and we shall get back to you as soon as possible.

    Published on: 2018-05-25 16:05:32

    Comment Form is loading comments...