Thread safety refers to the concept of ensuring that shared data or resources are accessed and modified correctly when multiple threads execute concurrently. A piece of code or a method is considered thread-safe if it ensures correct and predictable behaviour when accessed by multiple threads simultaneously, regardless of the order or timing of their execution.

Example - Race condition

In this example, we would like to have two threads and loop the increment of count for 2000 times. The desired output of the program is 2000.

class Counter {
	public int count;
	
	public void increment() {
		count++;
	}
}
 
public class Demo {
	public static void main(String[] args) {
		Counter c = new Counter();
		
		Runnable obj1 = () -> {
			for(int i=0; i<1000; i++) {
				c.increment();
			}
		};
		Runnable obj2 = () -> {
			for (int i=0; i<1000; i++) {
				c.increment();
			}
		};
		
		Thread t1 = new Thead(obj1);
		Thread t2 = new Thread(obj2);
		
		t1.start();
		t2.start();
		
		System.out.println(c.count);
	}
}

The two threads are correctly set up and when we execute the program we are expecting 2000 to be printed in the console. However, the result displayed as below.

1823

And when we execute this program again it shows different outcome.

246

This is because when main() kicks off the two threads for execution, the main continues its job and prints the count while the two threads might still executing. To fix the issue we have to let main to wait two threads have finished their execution and then print the result. The join() method in Thread class allows a thread to finished its execution before the next line of code is executed. It throws an InterruptedException. We modify our code as below.

public class Demo {
	public static void main(String[] args) throws InterruptedException {
		Counter c = new Counter();
		
		Runnable obj1 = () -> {
			for(int i=0; i<1000; i++) {
				c.increment();
			}
		};
		Runnable obj2 = () -> {
			for (int i=0; i<1000; i++) {
				c.increment();
			}
		};
		
		Thread t1 = new Thead(obj1);
		Thread t2 = new Thread(obj2);
		
		t1.start();
		t2.start();
		
		// Ensure t1 and t2 finish before reading count
		t1.join();
		t2.join();
		
		System.out.println(c.count);
	}
}

Now we run the program and observe the result.

1734

We are expecting 2000 but this time we got 1734. This is caused by two threads are executing the increment() at the same time. The count++ works by count = count + 1, this involves three steps:

  • Read the current value of count.
  • Add 1 to that value.
  • Write the new value back to count.

When two threads execute count++ simultaneously, both threads might read the same initial value (e.g., count = 3) before either has had a chance to update it. As a result, only one increment is effectively applied, leading to an incorrect outcome (e.g., count = 4 instead of 5).

Resolution - synchornise

As discussed, the inconsistency occurs when two threads are accessing the shared resource at the same time. To mitigate this we have to prevent simultaneous access of shared resource. Java provides the synchronized keyword, which associate a lock with the method or object that only the thread holding the lock can execute the synchronised method or block. Other threads attempting to access it must wait until the lock is released. We can modify our code as below.

class Counter {
	public int count;
	
	public synchronized void increment() {
		count++;
	}
}

This modification ensures the increment() method is only called once at a time.


Back to parent page: Java

Web_and_App_Development Java Multithreading Thread_Safe