Java Synchronized: What Is Thread Synchronization In Java

By Sruthy

By Sruthy

Sruthy, with her 10+ years of experience, is a dynamic professional who seamlessly blends her creative soul with technical prowess. With a Technical Degree in Graphics Design and Communications and a Bachelor’s Degree in Electronics and Communication, she brings a unique combination of artistic flair…

Learn about our editorial policies.
Updated March 7, 2024

This Tutorial Explains Thread Synchronization in Java along with Related Concepts like Java Lock, Race Condition, Mutex, Java Volatile & Deadlock in Java:

In a multithreading environment where multiple threads are involved, there are bound to be clashes when more than one thread tries to get the same resource at the same time. These clashes result in “race condition” and thus the program produces unexpected results.

For example, a single file is being updated by two threads. If one thread T1 is in the process of updating this file say some variable. Now while this update by T1 is still in progress, let’s say the second thread T2 also updates the same variable. This way the variable will give wrong results.

=> Watch Out The Complete Java Training Series Here.

Thread synchronization in Java

When multiple threads are involved, we should manage these threads in such a way that a resource can be accessed by a single thread at a time. In the above example, the file that is accessed by both the threads should be managed in such a way that T2 cannot access the file until T1 is done accessing it.

This is done in Java using “Thread Synchronization”.

Thread Synchronization In Java

As Java is a multi_threaded language, thread synchronization has a lot of importance in Java as multiple threads execute in parallel in an application.

We use keywords “synchronized” and “volatile” to achieve Synchronization in Java

We need synchronization when the shared object or resource is mutable. If the resource is immutable, then the threads will only read the resource either concurrently or individually.

In this case, we do not need to synchronize the resource. In this case, JVM ensures that Java synchronized code is executed by one thread at a time.

Most of the time, concurrent access to shared resources in Java may introduce errors like “Memory inconsistency” and “thread interference”. To avoid these errors we need to go for synchronization of shared resources so that the access to these resources is mutually exclusive.

We use a concept called Monitors to implement synchronization. A monitor can be accessed by only one thread at a time. When a thread gets the lock, then, we can say the thread has entered the monitor.

When a monitor is being accessed by a particular thread, the monitor is locked and all the other threads trying to enter the monitor are suspended until the accessing thread finishes and releases the lock.

Going forward, we will discuss synchronization in Java in detail in this tutorial. Now, let us discuss some basic concepts related to synchronization in Java.

Race Condition In Java

In a multithreaded environment, when more than one thread tries to access a shared resource for writing simultaneously, then multiple threads race each other to finish accessing the resource. This gives rise to ‘race condition’.

One thing to consider is that there is no problem if multiple threads are trying to access a shared resource only for reading. The problem arises when multiple threads access the same resource at the same time.

Race conditions occur due to a lack of proper synchronization of threads in the program. When we properly synchronize the threads such that at a time only one thread will access the resource, and the race condition ceases to exist.

So how do we detect the Race Condition?

The best way to detect race condition is by code review. As a programmer, we should review the code thoroughly to check for potential race conditions that might occur.

Locks/Monitors In Java

We have already mentioned that we use monitors or locks to implement synchronization. The monitor or lock is an internal entity and is associated with every object. So whenever a thread needs to access the object, it has to first acquire the lock or monitor of its object, work on the object and then release the lock.

Locks in Java will look as shown below:

public class Lock {
		private boolean isLocked = false;
		public synchronized void lock() throws InterruptedException {
			while(isLocked) {
				wait();
			}
			isLocked = true;
		}	
		public synchronized void unlock(){
			isLocked = false;
			notify();
		}
	}

As shown above, we have a lock () method that locks the instance. All the threads calling the lock () method will be blocked until the unblock () method sets are locked flag to false and notifies all the waiting threads.

Some pointers to remember about locks:

  1. In Java, each object has a lock or a monitor. This lock can be accessed by a thread.
  2. At a time only one thread can acquire this monitor or lock.
  3. Java programming language provides a keyword Synchronized’ that allows us to synchronize the threads by making a block or method as Synchronized.
  4. The shared resources that the threads need to access are kept under this Synchronized block/method.

Mutexes In Java

We already discussed that in a multithreaded environment, race conditions may occur when more than one thread tries to access the shared resources simultaneously and the race conditions result in unexpected output.

The part of the program that tries to access the shared resource is called the “Critical Section”. To avoid the occurrence of race conditions, there is a need to synchronize access to the critical section. By synchronizing this critical section, we make sure that only one thread can access the critical section at a time.

The simplest type of synchronizer is the “mutex”. Mutex ensures that at any given instance, only one thread can execute the critical section.

The mutex is similar to the concept of monitors or locks we discussed above. If a thread needs to access a critical section then it needs to acquire the mutex. Once mutex is acquired, the thread will access the critical section code, and when done, will release the mutex.

The other threads that are waiting to access the critical section will be blocked in the meantime. As soon as the thread holding mutex releases it, another thread will enter the critical section.

There are several ways in which we can implement a mutex in Java. 

  1. Using Synchronized Keyword
  2. Using Semaphore
  3. Using ReentrantLock

In this tutorial, we will discuss the first approach i.e. Synchronization. The other two approaches – Semaphore and ReentrantLock will be discussed in the next tutorial wherein we will discuss the java concurrent package.

Synchronized Keyword

Java provides a keyword “Synchronized” that can be used in a program to mark a Critical section. The critical section can be a block of code or a complete method. Thus, only one thread can access the critical section marked by the Synchronized keyword.

We can write the concurrent parts (parts that execute concurrently) for an application using the Synchronized keyword. We also get rid of the race conditions by making a block of code or a method Synchronized.

When we mark a block or method synchronized, we protect the shared resources inside these entities from simultaneous access and thereby corruption.

Types of Synchronization

There are 2 types of synchronization as explained below:

#1) Process Synchronization

Process Synchronization involves multiple processes or threads executing simultaneously. They ultimately reach a state where these processes or threads commit to a specific sequence of actions.

#2) Thread Synchronization

In Thread Synchronization, more than one thread is trying to access a shared space. The threads are synchronized in such a manner that the shared space is accessed only by one thread at a time.

The Process Synchronization is out of the scope of this tutorial. Hence we will be discussing only Thread Synchronization here.

In Java, we can use the synchronized keyword with:

  • A block of code
  • A method

The above types are the mutually exclusive types of thread synchronization. Mutual exclusion keeps the threads accessing shared data from interfering with each other.

The other type of thread synchronization is “InterThread communication” that is based on cooperation between threads. Interthread communication is out of the scope of this tutorial.

Before we go ahead with the synchronization of blocks and methods, let’s implement a Java program to demonstrate the behavior of threads when there is no synchronization.

Multi-threading Without Synchronization

The following Java program has multiple threads that are not synchronized.

class PrintCount {
   //method to print the thread counter
    public void printcounter() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Counter ==> "  + i );
         }
      } catch (Exception e) {
         System.out.println("Thread  interrupted.");
      }
   }
}
//thread class
class ThreadCounter extends Thread {
   private Thread t;
   private String threadName;
   PrintCount  PD;
   //class constructor for initialization
   ThreadCounter( String name,  PrintCount pd) {
      threadName = name;
      PD = pd;
   }
   //run method for thread
   public void run() {
      PD.printcounter();
      System.out.println("Thread " +  threadName + " exiting.");
   }
    //start method for thread
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
public class Main {
   public static void main(String args[]) {
      PrintCount PD = new PrintCount();
        //create two instances of thread class
      ThreadCounter T1 = new ThreadCounter( "ThreadCounter_1 ", PD );
      ThreadCounter T2 = new ThreadCounter( "ThreadCounter_2 ", PD );
      //start both the threads
      T1.start();
      T2.start();      // wait for threads to end
         try {
         T1.join();
         T2.join();
      } catch ( Exception e) {
         System.out.println("Interrupted");
      }
   }
}

Output

Multi-threading without Synchronization

From the output, we can see that as the threads are not synchronized the output is inconsistent. Both the threads start and then they display the counter one after the other. Both the threads exit at the end.

From the given program, the first thread should have exited after displaying the counter values, and then the second thread should have begun to display the counter values.

Now let’s go for synchronization and begin with code block synchronization.

Synchronized Code Block

A synchronized block is used to synchronize a block of code. This block usually consists of a few lines. A synchronized block is used when we do not want an entire method to be synchronized.

For example, we have a method with say 75 lines of code. Out of this only 10 lines of code are required to be executed by one thread at a time. In this case, if we make the entire method as synchronized, then it will be a burden on the system. In such situations, we go for synchronized blocks.

The scope of the synchronized method is always smaller than that of a synchronized method. A synchronized method locks an object of a shared resource that is to be used by multiple threads.

The general syntax of a synchronized block is as shown below:

synchronized (lock_object){
		//synchronized code statements
	}

Here “lock_object” is an object reference expression on which the lock is to be obtained. So whenever a thread wants to access the synchronized statements inside the block for execution, then it has to acquire the lock on the ‘lock_object’ monitor.

As already discussed, the synchronized keyword ensures that only one thread can acquire a lock at a time and all the other threads have to wait till the thread holding the lock finishes and releases the lock.

Note

  • A “NullPointerException” is thrown if the lock_object used is Null.
  • If a thread sleeps while still holding the lock, then the lock is not released. The other threads will not be able to access the shared object during this sleep time.

Now we will present the above example that was already implemented with slight changes. In the earlier program, we did not synchronize the code. Now we will use the synchronized block and compare the output.

Multi-threading With Synchronization

In the Java program below, we use a synchronized block. In the run method, we synchronize the code of lines that print the counter for each thread.

class PrintCount {
   //print thread counter
   public void printCounter() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Counter ==>  "  + i );
         }
      } catch (Exception e) {
         System.out.println("Thread  interrupted.");
      }
   }
}
//thread class
class ThreadCounter extends Thread {
   private Thread t;
   private String threadName;
   PrintCount  PD;
   //class constructor for initialization    
   ThreadCounter( String name,  PrintCount pd) {
      threadName = name;
      PD = pd;
   }
   //run () method for thread with synchronized block
   public void run() {
      synchronized(PD) {
         PD.printCounter();
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
    //start () method for thread
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
public class Main {
   public static void main(String args[]) {
      PrintCount PD = new PrintCount();
      //create thread instances
      ThreadCounter T1 = new ThreadCounter( "Thread_1 ", PD );
      ThreadCounter T2 = new ThreadCounter( "Thread_2 ", PD );
      //start both the threads
      T1.start();
      T2.start();      // wait for threads to end
      try {
         T1.join();
         T2.join();
      } catch ( Exception e) {
         System.out.println("Interrupted");
      }
   }
}

Output

Multi-threading with Synchronization

Now the output of this program using synchronized block is quite consistent. As expected, both the threads start executing. The first thread finished displaying the counter values and exits. Then the second thread displays the counter values and exits.

Synchronized Method

Let’s discuss the synchronized method in this section. Earlier we have seen that we can declare a small block consisting of fewer code lines as a synchronized block. If we want the entire function to be synchronized, then we can declare a method as synchronized.

When a method is made synchronized, then only one thread will be able to make a method call at a time.

The general syntax for writing a synchronized method is:

<access_modifier> synchronized method_name (parameters){
		//synchronized code
	}	

Just like a synchronized block, in the case of a synchronized method, we need a lock_object that will be used by threads accessing the synchronized method.

For the synchronized method, the lock object may be one of the following:

  • If the synchronized method is static, then the lock object is given by ‘.class’ object.
  • For a non-static method, the lock object is given by the current object i.e. ‘this’ object.

A peculiar feature of the synchronized keyword is that it is re-entrant. This means a synchronized method can call another synchronized method with the same lock. So a thread holding the lock can access another synchronized method without having to acquire a different lock.

The Synchronized Method is demonstrated using the below example.

class NumberClass {
    //synchronized method to print squares of numbers
    synchronized void printSquares(int n) throws InterruptedException 
    {
        //iterate from 1 to given number and print the squares at each iteration
        for (int i = 1; i <= n; i++) 
        {
            System.out.println(Thread.currentThread().getName() + " :: "+  i*i);
            Thread.sleep(500);
        }
    }
}
public class Main {
    public static void main(String args[])   {
        final NumberClass number = new NumberClass();
       //create thread
        Runnable thread = new Runnable()   {
            public void run()   {
                try {
                    number.printSquares(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //start thread instance
        new Thread(thread, "Thread One").start();
        new Thread(thread, "Thread Two").start();
    }
}

Output

synchronized method

In the above program, we have used a synchronized method to print the squares of a number. The upper limit of the number is passed to the method as an argument. Then starting from 1, the squares of each number are printed till the upper limit is reached.

In the main function, the thread instance is created. Each thread instance is passed a number to print squares.

As mentioned above, when a method to be synchronized is static, then the lock object is involved in the class and not the object. This means that we will lock on the class and not on the object. This is called static synchronization.

Another example is given below.

class Table{    
  //synchronized static method to print squares of numbers
  synchronized static void printTable(int n){  
   for(int i=1;i<=10;i++){  
     System.out.print(n*i + " ");  
     try{  
       Thread.sleep(400);
     }catch(Exception e){}  
   } 
   System.out.println();
 }  
} 
 
//thread class Thread_One  
class Thread_One extends Thread{  
	public void run(){  
	    Table.printTable(2);  
	}  
}
  
//thread class Thread_Two
class Thread_Two extends Thread{  
    public void run(){ 
        Table.printTable(5);  
    }  
}  
  
public class Main{  
public static void main(String t[]){
        //create instances of Thread_One and Thread_Two
        Thread_One t1=new Thread_One ();  
        Thread_Two t2=new Thread_Two (); 
        //start each thread instance
        t1.start();  
        t2.start();  
    }  
}  

Output

static method synchronization output

In the above program, we print multiplication tables of numbers. Each number whose table is to be printed is a thread instance of different thread class. Thus we print multiplication tables of 2 & 5, so we have two classes’ thread_one and thread_two to print the tables 2 and 5 respectively.

To summarize, the Java synchronized keyword performs the following functions:

  1. The synchronized keyword in Java guarantees mutually exclusive access to shared resources by providing a locking mechanism. Locking also prevents race conditions.
  2. Using the synchronized keyword, we prevent concurrent programming errors in code.
  3. When a method or block is declared as synchronized, then a thread needs an exclusive lock to enter the synchronized method or block. After performing the necessary actions, the thread releases the lock and will flush the write operation. This way it will eliminate memory errors related to inconsistency.

Volatile In Java

A volatile keyword in Java is used to make classes thread-safe. We also use the volatile keyword to modify the variable value by different threads. A volatile keyword can be used to declare a variable with primitive types as well as objects.

In certain cases, a volatile keyword is used as an alternative for the synchronized keyword but note that it is not a substitute for the synchronized keyword.

When a variable is declared volatile, its value is never cached but is always read from the main memory. A volatile variable guarantees ordering and visibility. Although a variable can be declared as volatile, we cannot declare classes or methods as volatile.

Consider the following block of code:

class ABC{
 static volatile int myvar =10;  
}

In the above code, the variable myvar is static and volatile. A static variable is shared among all the class objects. The volatile variable always resides in the main memory and is never cached.

Hence there will be only one copy of myvar in the main memory and all read/write actions will be done on this variable from the main memory. If myvar was not declared as volatile, then each thread object would have a different copy that would result in inconsistencies.

Some of the differences between Volatile and Synchronized keywords are listed below.

Volatile KeywordSynchronized keyword
The volatile keyword is used with variables only.The synchronized keyword is used with code blocks and methods.
A volatile keyword cannot block the thread for waiting. The synchronized keyword can block the thread for waiting.
Thread performance is improved with Volatile.Thread performance somewhat degrades with synchronized.
Volatile variables reside in the main memory.Synchronized constructs do not reside in the main memory.
Volatile synchronizes one variable between thread memory and main memory at a time.Synchronized keyword synchronizes all the variables at once.

Deadlock In Java

We have seen that we can synchronize multiple threads using synchronized keyword and make programs thread-safe. By synchronizing the threads, we ensure that the multiple threads execute simultaneously in a multi-threaded environment.

However, sometimes a situation occurs in which threads can no longer function simultaneously. Instead, they wait endlessly. This occurs when one thread waits on a resource and that resource is blocked by the second thread.

The second thread, on the other hand, is waiting on the resource that is blocked by the first thread. Such a situation gives rise to “deadlock” in Java.

Deadlock in Java is depicted using the below image.

Deadlock in Java

As we can see from the above diagram, thread A has locked the resource r1 and is waiting for resource r2. Thread B, on the other hand, has blocked resource r2 and is waiting on r1.

Thus none of the threads can finish their execution unless they get hold of the pending resources. This situation has resulted in the deadlock where both the threads are waiting endlessly for the resources.

Given below is an example of Deadlocks in Java.

public class Main {  
  public static void main(String[] args) {  
    //define shared resources
    final String shared_res1 = "Java tutorials";  
    final String shared_res2 = "Multithreading";  
    // thread_one => locks shared_res1 then shared_res2  
    Thread thread_one = new Thread() {  
      public void run() {  
          synchronized (shared_res1) {  
           System.out.println("Thread one: locked shared resource 1");  
  
           try { Thread.sleep(100);} catch (Exception e) {}  
  
           synchronized (shared_res2) {  
            System.out.println("Thread one: locked shared resource 2");  
           }  
         }  
      }  
    };  
    // thread_two=> locks shared_res2 then shared_res1  
    Thread thread_two = new Thread() {  
      public void run() {  
	  synchronized (shared_res2) {  
          System.out.println("Thread two: locked shared resource 2");  
	  
          try { Thread.sleep(100);} catch (Exception e) {}  
         synchronized (shared_res1) {  
            System.out.println("Thread two: locked shared resource 1");  
          }  
        }  
      }  
    };  
  
    //start both the threads  
    thread_one.start();  
    thread_two.start();  
  }  
}

Output

Deadlocks in Java - Output

In the above program, we have two shared resources and two threads. Both threads try to access the shared resources one by one. The output shows both the threads locking one resource each while waiting for the others. Thereby creating a deadlock situation.

Although we cannot stop deadlock situations from occurring completely, we can certainly avoid them by taking some steps.

Enlisted below are the means using which we can avoid deadlocks in Java.

#1) By avoiding nested locks

Having nested locks is the most important reason for having deadlocks. Nested locks are the locks that are given to multiple threads. Thus we should avoid giving locks to more than one thread.

#2) Use thread Join

We should use Thread.join with maximum time so that the threads can use the maximum time for execution. This will prevent deadlock that mostly occurs as one thread continuously waits for others.

#3) Avoid unnecessary lock

We should lock only the necessary code. Having unnecessary locks for the code can lead to deadlocks in the program. As deadlocks can break the code and hinder the flow of the program we should be inclined to avoid deadlocks in our programs.

Frequently Asked Questions

Q #1) What is Synchronization and why is it important? 

Answer: Synchronization is the process of controlling the access of a shared resource to multiple threads. Without synchronization, multiple threads can update or change the shared resource at the same time resulting in inconsistencies.

Thus we should ensure that in a multi-threaded environment, the threads are synchronized so that the way in which they access the shared resources is mutually exclusive and consistent.

Q #2) What is Synchronization and Non – Synchronization in Java?

Answer: Synchronization means a construct is a thread-safe. This means multiple threads cannot access the construct (code block, method, etc.) at once.

Non-Synchronized constructs are not thread-safe. Multiple threads can access the non-synchronized methods or blocks at any time. A popular non- synchronized class in Java is StringBuilder.

Q #3) Why is Synchronization required?

Answer: When processes need to execute concurrently, we need synchronization. This is because we need resources that may be shared among many processes.

In order to avoid clashes between processes or threads for accessing shared resources, we need to synchronize these resources so that all the threads get access to resources and the application also runs smoothly.

Q #4) How do you get a Synchronized ArrayList?

Answer: We can use Collections.synchronized list method with ArrayList as an argument to convert ArrayList to a synchronized list.

Q #5) Is HashMap Synchronized?

Answer: No, HashMap is not synchronized but HashTable is synchronized.

Conclusion

In this tutorial, we have discussed the Synchronization of threads in detail. Along with it, we also learned about the volatile keyword and deadlocks in Java. Synchronization consists of Process and Thread synchronization.

In a multithreading environment, we are more concerned with thread synchronization. We have seen the synchronized keyword approach of thread synchronization here.

Deadlock is a situation wherein multiple threads endlessly wait for resources. We have seen the example of deadlocks in Java along with the methods to avoid deadlocks in Java.

=> Visit Here To Learn Java From Scratch.

Was this helpful?

Thanks for your feedback!

Leave a Comment