Java Synchronization and Thread Safety Tutorial with Examples

One of Java’s many strengths come from the fact that it supports multithreading by default as has so from the very onset. One of the mechanisms that Java uses for this is via synchronization. When we use the synchronized keyword in Java we are trying limit the number of threads that can simultaneously access and modify a shared resource. The mechanism that is used in Java’s synchronization is called a monitor or lock. A monitor controls concurrent access to objects by allowing threads to have mutual exclusion and be able to wait(block) until certain conditions are met. More on this later…

Thread Safety

We say that code is thread-safe if it manipulates critical shared data in a manner that guarantees safe execution by multiple threads without causing any race conditions. We can ensure thread safety using a variety of methods:

  • Synchronization

    Exclusive locking through synchronization is one of the primary mechanisms used in Java via the synchronized keyword.

  • Explicit Locks

    Using the java.util.concurrent.locks package will provide more extensive locking operations than are available via the synchronized keyword in Java. One great feature of these locks over synchronization is their ability to back out should an attempt to obtain a lock fails.

  • Atomic Variables

    The java.util.concurrent.atomic package supports atomic operations on single variables thus preventing thread interference and memory consistency errors. Examples include using AtomicBoolean, AtomicInteger, AtomicLong and AtomicReference.

  • Volatile Variables

    Volatile is not a replacement of synchronized keyword. However, in certain situations using volatile in applications when one thread will be making changes to the variables and the others all reading or consumers of the data is a very good alternative. For a good refresher, please visit my post on Examining Volatile Keyword with Java Threads.

  • Immutable Objects

    If done correctly, creating immutable objects that are thread safe can be a godsend. Immutable objects by default are thread-safe, since once created, they can not be changed. However, one must realize that although the class itself is thread-safe the references to the class may not be. In order to ensure thread safety the use of synchronized keyword on the getter and setter methods for the reference should be used. Another alternative that would solve this problem would be to use the java.util.concurrent.atomic.AtomicReference.

Thread Safety Definition

I highly recommend getting the book, “Java Concurrency in Practice“, by Brian Goetz for a good read.

According to the author, “A class is thread safe if it behaves correctly when accessed from multiple threads, regardless of the scheduling or interleaving of the execution of those threads by the runtime environment, and with no additional synchronization or other coordination on the part of the calling code.

Java Synchronization

As mentioned above, the mechanism that Java uses to ensure thread safety is called synchronization. If multiple threads are allowed write access to a shared resource we can introduce data inconsistencies when one thread is modifying the critical structure and another comes by and attempts to modify the same resource at the same time. You will notice that I mentioned write access not read access. There is no problems when multiple threads are reading the shared resource, the problems really arises when writing is involved as the data is being changed.

Anytime we discuss synchronization in Java we need to ensure we discuss the topic of a critical section. A critical section is a block of code that can not be accessed by more than one thread at a time because it is accesses a shared resource. Whenever a thread wants access to these critical sections it must do so using one the synchronization mechanisms. It will interrogate the object’s monitor to ensure that other threads are not inside this critical section. If there are none, it may enter the critical section and makes the necessary changes to the shared resource. If there is another thread already there, it will wait(block) until the thread completes. When there are completing threads waiting for the same object monitor, the JVM chooses one of them, the rest will continue to wait.

Note

Please limit the use of synchronized keyword as you will incur a performance hit. Try and synchronize only the block of code that absolutely needs it.

To minimize some of the overhead of Synchronization

  • Only synchronize the critical sections – If you must synchronize a critical section to ensure thread safety, then keep synchronization blocks as small as possible.
  • Make use of immutable objects as much as possible – Immutable classes are, by default, thread-safe because there is no way to change any of the class’ data structures once created.
  • Use private fields – Making fields private protects them from access from the outside and limits any unsynchronized access to them.
  • Use wrapper classes that are thread safe – Using the synchronization wrappers add automatic synchronization and thread-safety to the collection classes.
  • Make sure to synchronize access to certain Java variables – The Java types long and double are comprised of eight bytes each; any access to these fields must be synchronized.

Synchronized Scope in Java

Synchronized Method

In order to synchronize a method you must use the synchronized keyword to control access concurrent access to the method. When a thread tries to access a synchronized method it will acquire the lock so long as it is available and not used by another thread. As soon as it exits the methods the lock is released and available for other threads to enter the method. Remember, if you assign the synchronized keyword to a method you are declaring the entire method as a critical section. I would strongly discourage you from using this option if the method contains anything more than a few statements.

public synchronized void increment() {
  counter++;
}

Synchronized Block

Instead of protecting the entire method we use the synchronized keyword to protect access to a block of code. By using a synchronized block we can still protect the shared resource from concurrent access while leaving the rest of the statements outside the block to improve performance. As stated earlier, our goal should be to keep the critical section as short as possible. When using the synchronized block syntax we need to pass an object reference as a parameter. As you see in the example below, you will use the this keyword to reference the object that executes the block, but you can use other object references, in my case I created a lock Object.

synchronized(this) {
  // critical section of code
  ...
}

or 

private Object lock = new Object();
synchronized(lock) {
  // critical section of code
  ...
}

Java Synchronization Issues

Warning

Remember the more synchronization blocks you have in your code the higher the chances of you running into the following issues:

  • Risk of Deadlock
  • Risks of Starvation
  • Risks of LiveLock
  • Added Program Complexity
  • Slower Application performance

Java Threads without Synchronization Example

package com.avaldes.tutorials;

public class SynchronizationProblemExample {

  private static int counter = 0;
  private static volatile boolean isActive = true;
  
  // *** CRITICAL SECTION ***
  public static int increment() {
    counter++;
    return counter;
  }
  
  public static void main(String[] args) {
    Thread t1 = new Thread(new Worker(), "Thread_1");
    Thread t2 = new Thread(new Worker(), "Thread_2");
    Thread t3 = new Thread(new Worker(), "Thread_3");
    Thread t4 = new Thread(new Worker(), "Thread_4");
    Thread t5 = new Thread(new Worker(), "Thread_5");
    
    t1.start();
    t2.start();
    t3.start();   
    t4.start();   
    t5.start();   
    
    //  Make the Main Thread sleep for 100 milliseconds
    //  then set isActive to false to stop all threads 
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    isActive = false;
    
  }
  
  private static class Worker implements Runnable {
    public void run() {
      // tight loop using volatile variable as active flag for proper shutdown
      while (isActive) {
        try {
            doWork();
        } catch (Exception e) {
          System.out.format("%s was interrupted...\n", Thread.currentThread().getName());
          e.printStackTrace();
        }
      }
    }
    
    private void doWork() {
      System.out.format("Current runCount is %05d...\n", increment());
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

Output Without Synchronization

As you can see in the following execution when we run our example in the absence synchronization or atomicInteger class we will get data inconsistency errors as we have multiple threads modifying the critical section that performs the incrementing of the counter. In addition, to the duplicates which will be highlighted for you, you will notice that some counts have been missed altogether. These include counts of 1, 11, 12 and 16.

Current runCount is 00002...
Current runCount is 00002...
Current runCount is 00003...
Current runCount is 00004...
Current runCount is 00005...
Current runCount is 00006...
Current runCount is 00007...
Current runCount is 00008...
Current runCount is 00008...
Current runCount is 00009...
Current runCount is 00010...
Current runCount is 00013...
Current runCount is 00013...
Current runCount is 00013...
Current runCount is 00014...
Current runCount is 00015...
Current runCount is 00017...
Current runCount is 00017...
...

Fixing the problem by adding Synchronized to Method Example

// *** CRITICAL SECTION ***
public static synchronized int increment() {
  counter++;
  return counter;
}

You have the option of fixing the problem by synchronizing on the method or a block. Since this method is only one line of code plus a return either method will work fine. However, for methods that contain many lines of code, the synchronization block is always the preferred choice in order to try to keep the block (critical section) as compact as possible.

Fixing the problem by adding Synchronized Block to Static Method

// *** CRITICAL SECTION ***
public static int increment() {
  synchronized (SynchronizationProblemExample.class) {
    counter++;
    return counter;
  }
}

Output with Synchronization — Corrected Now

Current runCount is 00001...
Current runCount is 00002...
Current runCount is 00003...
Current runCount is 00004...
Current runCount is 00005...
Current runCount is 00006...
Current runCount is 00007...
Current runCount is 00008...
Current runCount is 00009...
Current runCount is 00010...
Current runCount is 00011...
Current runCount is 00012...
Current runCount is 00013...
Current runCount is 00014...
Current runCount is 00015...
Current runCount is 00016...
Current runCount is 00017...
Current runCount is 00018...
Current runCount is 00019...
Current runCount is 00020...
Current runCount is 00021...
Current runCount is 00022...
Current runCount is 00023...
Current runCount is 00024...
Current runCount is 00025...
Current runCount is 00026...
Current runCount is 00027...
Current runCount is 00028...
Current runCount is 00029...
Current runCount is 00030...
Current runCount is 00031...
Current runCount is 00032...
Current runCount is 00033...
Current runCount is 00034...
Current runCount is 00035...
Current runCount is 00036...
Current runCount is 00037...
Current runCount is 00038...
Current runCount is 00039...
Current runCount is 00040...
Current runCount is 00041...
Current runCount is 00042...
Current runCount is 00043...
Current runCount is 00044...
Current runCount is 00045...
Current runCount is 00046...
Current runCount is 00047...
Current runCount is 00048...
Current runCount is 00049...
Current runCount is 00050...

Related Posts

  • Java Thread, Concurrency and Multithreading Tutorial
    This Java Thread tutorial will give you a basic overview on Java Threads and introduce the entire tutorial series on concurrency and multithreading. From here, you will learn about many java thread concepts like: Thread States, Thread Priority, Thread Join, and ThreadGroups. In addition, you will learn about using the volatile keyword and examples on using wait, notify and notifyAll.
  • Java Thread States - Life Cycle of Java Threads
    Get a basic understanding of the various thread states. Using the state transition diagram we show the various states for a Java thread and the events that cause the thread to jump from one state to another.
  • Creating Java Threads Example
    In this post we cover creating Java Threads using the two mechanisms provided in Java, that is, by extending the Thread class and by implementing Runnable interface for concurrent programming.
  • Java Thread Priority Example
    In this post we cover Thread priorities in Java. By default, a java thread inherits the priority (implicit) of its parent thread. Using the setPriority() method you can increase or decrease the thread priority of any java thread.
  • Java ThreadGroup Example
    Sometimes we will need to organize and group our threads into logical groupings to aid in thread management. By placing threads in a threadGroup all threads in that group can be assigned properties as a set, instead of going through the tedious task of assigning properties individually.
  • Java Thread Sleep Example
    We seem to use this method very often to temporarily suspend the current threads execution for a specific period of time. Let's spend some time and familiarize ourselves with what this method actually does.
  • Java Thread Join Example
    In Java, using Thread.join() causes the current thread to wait until the specified thread dies. Using this method allows us to impose an order such that we can make one thread wait until the other completes doing what it needed to do, such as completing a calculation.
  • Examining Volatile Keyword with Java Threads
    When we declare a field as volatile, the JVM will guarantee visibility, atomicity and ordering of the variable. Without it the data may be cached locally in CPU cache and as a result changes to the variable by another thread may not be seen by all other threads resulting in inconsistent behaviour.
  • Java Threads Wait, Notify and NotifyAll Example
    The purpose of using notify() and notifyAll() is to enable threads to communicate with one another via some object on which to performing the locking. A thread using the wait() method must own a lock on the object. Once wait() is called, the thread releases the lock, and waits for another thread to either call notify() or notifyAll() method.
  • Java Thread Deadlock Example and Thread Dump Analysis using VisualVM
    Deadlock is a condition where several threads are blocking forever, waiting for the other to finish but they never do. This tutorial will discuss situations that will lead to Java Thread deadlock conditions and how they can be avoided. In addition, we will discuss using Java VisualVM to pinpoint and analyze the source of the deadlock conditions.
  • Java Thread Starvation and Livelock with Examples
    Starvation occurs when a thread is continually denied access to resources and as a result it is unable to make progress. Thread liveLock is a condition that closely resembles deadlock in that several processes are blocking each other. But with livelock, a thread is unable to make any progress because every time it tries the operation always fails.
  • Java Synchronization and Thread Safety Tutorial with Examples
    One of Java's many strengths come from the fact that it supports multithreading by default as has so from the very onset. One of the mechanisms that Java uses for this is via synchronization. When we use the synchronized keyword in Java we are trying limit the number of threads that can simultaneously access and modify a shared resource. The mechanism that is used in Java's synchronization is called a monitor.
  • Creating a Thread Safe Singleton Class with Examples
    In this tutorial we cover many examples of creating thread-safe singleton classes and discuss some of the shortfalls of each and provide some recommendations on best approaches for a fast, efficient and highly concurrent solution.
  • Java Threads and Concurrent Locks with Examples
    In this tutorial we will focus primarily on using the concurrent utilities and how these can make concurrent programming easier for us.

Please Share Us on Social Media

Facebooktwitterredditpinterestlinkedinmail

Leave a Reply

Your email address will not be published. Required fields are marked *