Updates
  • Starting New Weekday Batch for Java Full Stack Developer on 24th June 2024 @ 02:00 PM to 04:00 PM
  • Starting New Weekend Batch for Java Full Stack Developer on 06th July 2024 @ 11:00 AM to 02:00 PM
  • Starting New Weekday Batch for Java Full Stack Developer on 08th July 2024 @ 04:00 PM to 06:00 PM
  • Starting New Weekday Batch for Java Full Stack Developer on 29th July 2024 @ 10:00 AM to 12:00 PM
Join Course

Thread Synchronization

In this tutorial, we will discuss Thread Synchronization. Before delving into the discussion on synchronization concepts, it's essential to understand the deadlock condition.

Deadlock Condition:

Let's consider a scenario where two threads, t1 and t2, are holding resources R1 and R2, respectively. Thread t1 is requesting resource R2, and thread t2 is requesting R1. Thread t1 will release resource R1 only after acquiring R2, and thread t2 will release R2 only after acquiring R1.

Thread-Synchronization:

In Java, when many threads attempt to access the same resources, multi-threaded programs may frequently encounter issues that lead to unexpected and incorrect outcomes. To resolve this problem, synchronization comes into the picture. In Java, every object has a lock, but it becomes visible only when we access a synchronized context with that object.
When we enter a synchronized context with an object, the current working object is locked by the current thread. As a result, other threads cannot request the same object until it is released by the first thread.

Types of Synchronization:

1. Method Level Synchronization
2. Block Level Synchronization
3. Class Level Synchronization

1. Method Level Synchronization:- Here we use synchronized keyword at the time of instance method declaration.

            
class A{
	void m1(){
		for(int i = 0; i <= 15;i++){
			System.out.println("Current Thread is : "+Thread.currentThread().getName()+" ---> "+i);
			try{
				Thread.sleep(500);
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
}
class Thread1 extends Thread{
	A a1;

	Thread1(A a1){
		this.a1 = a1;
	}

	public void run(){
		a1.m1();
	}
}
class Thread2 extends Thread{
	A a1;

	Thread2(A a1){
		this.a1 = a1;
	}

	public void run(){
		a1.m1();
	}
}
class JTC{
	public static void main(String arg[]){
		A a1 = new A();
		Thread1 t1 = new Thread1(a1);
		Thread2 t2 = new Thread2(a1);
		t1.setName("T-1");
		t2.setName("T-2");
		t1.start();
		t2.start();
		
	}
}
    Current Thread is : T-1 ---> 0
    Current Thread is : T-2 ---> 0
    Current Thread is : T-1 ---> 1
    Current Thread is : T-2 ---> 1
    Current Thread is : T-2 ---> 2
    Current Thread is : T-1 ---> 2
    Current Thread is : T-2 ---> 3
    Current Thread is : T-1 ---> 3
    Current Thread is : T-1 ---> 4
    Current Thread is : T-2 ---> 4
    Current Thread is : T-2 ---> 5
    Current Thread is : T-1 ---> 5
    Current Thread is : T-1 ---> 6
    Current Thread is : T-2 ---> 6
    Current Thread is : T-1 ---> 7
    Current Thread is : T-2 ---> 7
    Current Thread is : T-2 ---> 8
    Current Thread is : T-1 ---> 8
    Current Thread is : T-1 ---> 9
    Current Thread is : T-2 ---> 9
    Current Thread is : T-1 ---> 10
    Current Thread is : T-2 ---> 10
    Current Thread is : T-1 ---> 11
    Current Thread is : T-2 ---> 11
    Current Thread is : T-2 ---> 12
    Current Thread is : T-1 ---> 12
    Current Thread is : T-2 ---> 13
    Current Thread is : T-1 ---> 13
    Current Thread is : T-1 ---> 14
    Current Thread is : T-2 ---> 14
    Current Thread is : T-2 ---> 15
    Current Thread is : T-1 ---> 15
            

In this example, both Thread1 and Thread2 are invoking the m1() method, which is an instance method of class A, using the same object of class A. As observed in the output console, when a thread gets its CPU time, it invokes the m1 method

            
class A{
	synchronized void m1(){
		for(int i = 0; i <= 15;i++){
			System.out.println("Current Thread is : "+Thread.currentThread().getName()+" ---> "+i);
			try{
				Thread.sleep(500);
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
}
class Thread1 extends Thread{
	A a1;

	Thread1(A a1){
		this.a1 = a1;
	}

	public void run(){
		a1.m1();
	}
}
class Thread2 extends Thread{
	A a1;

	Thread2(A a1){
		this.a1 = a1;
	}

	public void run(){
		a1.m1();
	}
}
class JTC{
	public static void main(String arg[]){
		A a1 = new A();
		Thread1 t1 = new Thread1(a1);
		Thread2 t2 = new Thread2(a1);
		t1.setName("T-1");
		t2.setName("T-2");
		t1.start();
		t2.start();
		
	}
}
    Current Thread is : T-1 ---> 0
    Current Thread is : T-1 ---> 1
    Current Thread is : T-1 ---> 2
    Current Thread is : T-1 ---> 3
    Current Thread is : T-1 ---> 4
    Current Thread is : T-1 ---> 5
    Current Thread is : T-1 ---> 6
    Current Thread is : T-1 ---> 7
    Current Thread is : T-1 ---> 8
    Current Thread is : T-1 ---> 9
    Current Thread is : T-1 ---> 10
    Current Thread is : T-1 ---> 11
    Current Thread is : T-1 ---> 12
    Current Thread is : T-1 ---> 13
    Current Thread is : T-1 ---> 14
    Current Thread is : T-1 ---> 15
    Current Thread is : T-2 ---> 0
    Current Thread is : T-2 ---> 1
    Current Thread is : T-2 ---> 2
    Current Thread is : T-2 ---> 3
    Current Thread is : T-2 ---> 4
    Current Thread is : T-2 ---> 5
    Current Thread is : T-2 ---> 6
    Current Thread is : T-2 ---> 7
    Current Thread is : T-2 ---> 8
    Current Thread is : T-2 ---> 9
    Current Thread is : T-2 ---> 10
    Current Thread is : T-2 ---> 11
    Current Thread is : T-2 ---> 12
    Current Thread is : T-2 ---> 13
    Current Thread is : T-2 ---> 14
    Current Thread is : T-2 ---> 15
            

This example is similar to the first one, with only one difference: we declared the m1 method, which is an instance method of class A, along with the synchronized keyword. We invoke it from the run methods of both Thread1 and Thread2 classes, using the same object of class A. The effects can be observed in the output. In the console, we can see that T-1 gets the first CPU time and processes the m1 method of class A. After each iteration, T-1 moves to the sleep statement for 500 milliseconds. Meanwhile, T-2 is unable to process the m1 method until T-1 completes its task and releases the object lock. Only then does T-2 start its processing.

            
class A{
	synchronized void m1(){
		for(int i = 0; i <= 15;i++){
			System.out.println("Current Thread is : "+Thread.currentThread().getName()+" ---> "+i);
			try{
				Thread.sleep(500);
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
}
class Thread1 extends Thread{
	A a1;

	Thread1(A a1){
		this.a1 = a1;
	}

	public void run(){
		a1.m1();
	}
}
class Thread2 extends Thread{
	A a1;

	Thread2(A a1){
		this.a1 = a1;
	}

	public void run(){
		a1.m1();
	}
}
class JTC{
	public static void main(String arg[]){
		A a1 = new A();
		A a2 = new A();
		Thread1 t1 = new Thread1(a1);
		Thread2 t2 = new Thread2(a2);
		t1.setName("T-1");
		t2.setName("T-2");
		t1.start();
		t2.start();
		
	}
}
    Current Thread is : T-1 ---> 0
    Current Thread is : T-2 ---> 0
    Current Thread is : T-2 ---> 1
    Current Thread is : T-1 ---> 1
    Current Thread is : T-2 ---> 2
    Current Thread is : T-1 ---> 2
    Current Thread is : T-1 ---> 3
    Current Thread is : T-2 ---> 3
    Current Thread is : T-2 ---> 4
    Current Thread is : T-1 ---> 4
    Current Thread is : T-2 ---> 5
    Current Thread is : T-1 ---> 5
    Current Thread is : T-1 ---> 6
    Current Thread is : T-2 ---> 6
    Current Thread is : T-2 ---> 7
    Current Thread is : T-1 ---> 7
    Current Thread is : T-1 ---> 8
    Current Thread is : T-2 ---> 8
    Current Thread is : T-1 ---> 9
    Current Thread is : T-2 ---> 9
    Current Thread is : T-1 ---> 10
    Current Thread is : T-2 ---> 10
    Current Thread is : T-1 ---> 11
    Current Thread is : T-2 ---> 11
    Current Thread is : T-1 ---> 12
    Current Thread is : T-2 ---> 12
    Current Thread is : T-2 ---> 13
    Current Thread is : T-1 ---> 13
    Current Thread is : T-2 ---> 14
    Current Thread is : T-1 ---> 14
    Current Thread is : T-1 ---> 15
    Current Thread is : T-2 ---> 15
            

In this example, we invoke the synchronized instance method of class A, m1, using two different objects of class A, namely a1 and a2, by T-1 and T-2 threads. As we can see, both threads are able to process the m1 method simultaneously because each object of class A has its own lock. In this program, we have two object locks: a1 is associated with T-1, and a2 is associated with T-2.

2. Block Level Synchronization:-We use Block Level Synchronization when we don't want to synchronize the entire method implementation. Instead, we need to synchronize a specific part of the method as per the requirements.
We use local block to achieve Block Level Synchronization.

Syntax:-

synchronized (this){
}

As we can see in the above syntax we are using this keyword so current working object gets locked here.

            
class A{
	void m1(){
		try{
			
			synchronized(this){
				for(int i = 1; i <= 10; i++){
					System.out.println(Thread.currentThread().getName()+" is processing synchronized-block:- "+i);
					Thread.sleep(500);
				}
			}
			for(int i = 1; i <= 10; i++){
				System.out.println(Thread.currentThread().getName()+" is processing non-synchronized-block:- "+i);
				Thread.sleep(500);
			}	
		}catch(Exception e){
			e.printStackTrace();
		}
			
	}
}
class Thread1 extends Thread{
	A a1;
	Thread1(A a1){
		this.a1 = a1;
	}

	public void run(){
		a1.m1();
	}
}
class Thread2 extends Thread{
	A a1;
	Thread2(A a1){
		this.a1 = a1;
	}

	public void run(){
		a1.m1();
	}
}
class JTC{
	public static void main(String arg[]){
		A a1 = new A();
		Thread1 t1 = new Thread1(a1);
		Thread2 t2 = new Thread2(a1);
		t1.setName("T-1");
		t2.setName("T-2");
		t1.start();
		t2.start();
	}
}
    T-1 is processing synchronized-block:- 1
    T-1 is processing synchronized-block:- 2
    T-1 is processing synchronized-block:- 3
    T-1 is processing synchronized-block:- 4
    T-1 is processing synchronized-block:- 5
    T-1 is processing synchronized-block:- 6
    T-1 is processing synchronized-block:- 7
    T-1 is processing synchronized-block:- 8
    T-1 is processing synchronized-block:- 9
    T-1 is processing synchronized-block:- 10
    T-2 is processing synchronized-block:- 1
    T-1 is processing non-synchronized-block:- 1
    T-2 is processing synchronized-block:- 2
    T-1 is processing non-synchronized-block:- 2
    T-1 is processing non-synchronized-block:- 3
    T-2 is processing synchronized-block:- 3
    T-2 is processing synchronized-block:- 4
    T-1 is processing non-synchronized-block:- 4
    T-1 is processing non-synchronized-block:- 5
    T-2 is processing synchronized-block:- 5
    T-1 is processing non-synchronized-block:- 6
    T-2 is processing synchronized-block:- 6
    T-1 is processing non-synchronized-block:- 7
    T-2 is processing synchronized-block:- 7
    T-1 is processing non-synchronized-block:- 8
    T-2 is processing synchronized-block:- 8
    T-2 is processing synchronized-block:- 9
    T-1 is processing non-synchronized-block:- 9
    T-2 is processing synchronized-block:- 10
    T-1 is processing non-synchronized-block:- 10
    T-2 is processing non-synchronized-block:- 1
    T-2 is processing non-synchronized-block:- 2
    T-2 is processing non-synchronized-block:- 3
    T-2 is processing non-synchronized-block:- 4
    T-2 is processing non-synchronized-block:- 5
    T-2 is processing non-synchronized-block:- 6
    T-2 is processing non-synchronized-block:- 7
    T-2 is processing non-synchronized-block:- 8
    T-2 is processing non-synchronized-block:- 9
    T-2 is processing non-synchronized-block:- 10
            

As seen in this example, we are not synchronizing the entire m1 method; instead, we are using Block-Level Synchronization to synchronize a specific part of the m1 method. The effect can be observed on the output screen: first, T-1 gets its CPU time and processes its synchronized block. T-2 does not process the synchronized block of the m1 method until the T-1 thread completes the synchronized block.

Class Level Synchronization:-We know that when we use the synchronized keyword with an Instance Method, we get the object-level lock, whereas using the synchronized keyword with a Static Method results in a class-level lock. In Method Level Synchronization, we use the synchronized keyword with an Instance Method. When we access the same method with two different objects of the class using two different threads, concurrency will occur.
On the other hand, in Class Level Synchronization, when we use the synchronized keyword with a static method and invoke that method using two different objects similar to Method Level Synchronization, it will process one by one.

            
class A{
	synchronized static void m1(){
		for(int i = 1; i <= 15; i++){
			System.out.println(Thread.currentThread().getName()+" is processing :- "+i);
			try{
				Thread.sleep(500);
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
}
class Thread1 extends Thread{
	A a1;
	
	Thread1(A a1){
		this.a1 = a1;
	}

	public void run(){
		a1.m1();
	}
}
class Thread2 extends Thread{
	A a1;
	
	Thread2(A a1){
		this.a1 = a1;
	}
	
	public void run(){
		a1.m1();
	}
}
class JTC{
	public static void main(String arg[]){
		A a1 = new A();
		A a2 = new A();
		Thread1 t1 = new Thread1(a1);
		Thread2 t2 = new Thread2(a2);
		t1.setName("T-1");
		t2.setName("T-2");
		t1.start();
		t2.start();
	}
}
    T-1 is processing :- 1
    T-1 is processing :- 2
    T-1 is processing :- 3
    T-1 is processing :- 4
    T-1 is processing :- 5
    T-1 is processing :- 6
    T-1 is processing :- 7
    T-1 is processing :- 8
    T-1 is processing :- 9
    T-1 is processing :- 10
    T-1 is processing :- 11
    T-1 is processing :- 12
    T-1 is processing :- 13
    T-1 is processing :- 14
    T-1 is processing :- 15
    T-2 is processing :- 1
    T-2 is processing :- 2
    T-2 is processing :- 3
    T-2 is processing :- 4
    T-2 is processing :- 5
    T-2 is processing :- 6
    T-2 is processing :- 7
    T-2 is processing :- 8
    T-2 is processing :- 9
    T-2 is processing :- 10
    T-2 is processing :- 11
    T-2 is processing :- 12
    T-2 is processing :- 13
    T-2 is processing :- 14
    T-2 is processing :- 15
            

In this example, we have declared a static synchronized method in class A. Both threads, T-1 and T-2, are accessing the m1 method using the same objects of class A. As observed in the output, both threads are processing the m1 method one by one.

Note:When a thread moves to the wait state, the object lock is first released, and then the thread moves to the waiting state, unlike the sleep state.

            
class A{
	synchronized void m1(){
		for(int i = 1; i <= 15; i++){
			System.out.println(Thread.currentThread().getName()+" is processing :- "+i);
			try{
				this.wait(500);
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
}
class Thread1 extends Thread{
	A a1;
	
	Thread1(A a1){
		this.a1 = a1;
	}

	public void run(){
		a1.m1();
	}
}
class Thread2 extends Thread{
	A a1;
	
	Thread2(A a1){
		this.a1 = a1;
	}
	
	public void run(){
		a1.m1();
	}
}
class JTC{
	public static void main(String arg[]){
		A a1 = new A();
		Thread1 t1 = new Thread1(a1);
		Thread2 t2 = new Thread2(a1);
		t1.setName("T-1");
		t2.setName("T-2");
		t1.start();
		t2.start();
	}
}
    T-1 is processing :- 1
    T-2 is processing :- 1
    T-2 is processing :- 2
    T-1 is processing :- 2
    T-1 is processing :- 3
    T-2 is processing :- 3
    T-1 is processing :- 4
    T-2 is processing :- 4
    T-2 is processing :- 5
    T-1 is processing :- 5
    T-2 is processing :- 6
    T-1 is processing :- 6
    T-1 is processing :- 7
    T-2 is processing :- 7
    T-1 is processing :- 8
    T-2 is processing :- 8
    T-2 is processing :- 9
    T-1 is processing :- 9
    T-2 is processing :- 10
    T-1 is processing :- 10
    T-1 is processing :- 11
    T-2 is processing :- 11
    T-2 is processing :- 12
    T-1 is processing :- 12
    T-1 is processing :- 13
    T-2 is processing :- 13
    T-2 is processing :- 14
    T-1 is processing :- 14
    T-1 is processing :- 15
    T-2 is processing :- 15
            

In this example, we are placing the threads into a wait state for 500 milliseconds. Simultaneously, we are accessing the same synchronized method using the same object of class A by two different threads, T-1 and T-2, leading to concurrency.