How To Synchronize Multiple Threads In Java — Demystified

Amitrajit Bose
ITNEXT
Published in
4 min readAug 28, 2019

--

Do you get chills whenever somebody asks you to write a multi-threaded application? I used to. I had tried reading various books but there was still the same fear that prevailed until I tried implementing them and playing around with them. They are extremely exciting, will raise multiple curious questions and the best part is debugging them.

In this article, we will be discussing thread synchronization using Java Threads and will clear our concepts with a very simple example. The only prerequisites are understanding basic Java syntax and preliminary knowledge about Java Thread(or, you can refer to this quickly). So, let’s jump in!

Umm... Not Those Threads! Credits: Amirali

Threads are lightweight processes that can be executed parallelly in a time-shared manner. They are a way for a program to divide itself into two or more simultaneously (or pseudo-simultaneously) running tasks. Threads and processes differ from one OS to another but, usually, a thread is contained inside a process and different threads in the same process share same resources while different processes in the same multitasking OS do not.

Java provides a way of creating threads and synchronizing their task by using synchronized blocks. Let us start with our example.

We will be working with a Counter class, that has nothing but just a count member variable and a method incr() which increments the value of count variable by one.

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

Inside the main() we create a thread t1 using the Runnable object and overriding the run() method, inside which we increment the value of count 100 times.

It should print Count = 100 , right? But it doesn’t. Pause, think for a while!

The main() method is also running on one thread which is the default main thread in Java created by the JVM. When our thread t1 is inside the loop somewhere between 0 and 100, the main thread continues ahead (it does not wait) and prints the value of count at that instance. Pretty mesmerizing stuff.

We want the program to wait for the thread t1 to complete its execution. We will be making use of the join() method of the thread, which will make sure that t1 is terminated before the next instruction is executed by the program. Also, it throws InterruptedException which needs to be handled.

Cool! It prints Count = 100 as we needed. Let’s now take it to the next level and add another thread that can run in parallel.

We define another thread t2, and it does the same thing as t1. Now both of them increment the counter 1000 times, so our expected correct value should be 2000. Easy!

Run the above Java program on your local system (online compilers are slow, might give unexpected results) and you will find that the printed value for count is less than 2000.

Why does this happen?

The operation count++ is not atomic, at the low-level, the machine needs to issue multiple instructions for this one line of code. It may so happen that when the thread t1 incremented the counter from 1000 to 1001, and before it got updated to 1001, the thread t2 accessed the counter and again incremented it from 1000 to 1001. Thus, we get values less than or equal to 2000 in most cases. We can handle this with the help of the synchronized keyword.

Making the incr() method synchronized will make sure that only one thread can work with the method the other threads have to wait. Thus making it Thread-Safe.

Thus, the output produced is 2000 for the counter, the join() methods are still required because we want the print statement to wait till the execution of both the threads t1 and t2 are over.

Syntax Overview

We understood how to synchronize multiple threads in Java. Let us have a look at some syntax. For example, when we are synchronizing objects (say, sync_obj).

synchronized (sync_obj){
// code
// more code ..
// Access shared resources
...
}

Sometimes, we need to synchronize methods, maybe to stop it from being invoked by two or more thread simultaneously. For example, we want to synchronize the my_func() method.

public synchronized void my_func(){
// code
// more code ..
// Access shared resources
...
}

Also, sometimes we might need to synchronize a part of the method which can be done as follows.

public void my_func(){
System.out.println("Hello World!");
...
synchronized (this){
...
// Access shared resources
...
}
...
System.out.println("Goodbye!");
}

With this much knowledge, you are now well equipped to go dive in and experiment with multithreaded programs in Java. Godspeed!

Source: Unsplash

If this article helped you, please do consider clapping. Have a great day! :)

--

--

Engineering @ Flipkart, ex-Rakuten | Software Engineering, Algorithms, Data Science, ML, NLP