Thread Synchronization with the synchronized Keyword in Java
If your Java application is multithreaded and if it contains non thread safe code, then synchronization is a must. Synchronization deals with race condition and some other dangers. In most other modern programming languages synchronization in multithreaded programming is a very painful task. But in Java it is as easy and fun just like most other Java constructs. When using the keyword synchronized on a method or code section you do not need to explicitly acquire and release some mutex or lock. JVM will take care of everything when you use the synchronized keyword on a method or a block of code.
Critical Section and Race Condition
Imagine that you have a variable (static class field) called count on a class that different parts of your program need to read and update. You have two threads called T1 and T2. Now in the lifetime of your threads the T1 thread is reading the the value of count and it got 0 back. Now the T1 thread got busy doing other work. Now, T2 also read the variable and got 0 back. T2 is incrementing it by one and as a result the count should be written as 1. Now, the T1 has 0 as the value of count. In the middle of T2 incrementing it by 1 the T1 thread getting back from its business put the value of count as 1. After two increments it should have been 2. But in practice the value will be 1. The work was not properly synchronized.
The block of code that updates the count variable is a critical section of code. Two threads are running for updating the count variable and occurring a race condition. So, we can safely conclude, without providing some bookish definition, that the section of code that needs special care for interacting with different threads is a critical section and the condition for it is the race condition. Do not write this definition for any of your academic writings, otherwise you will get some penalty.
Note: Do not confuse race condition with deadlock. Deadlock is a different thing that we will discuss in a different article.
Synchronization
Synchronization is the mechanism of controlling access to or interaction with critical sections of code by different threads to avoid race conditions. So, the above mentioned issue can be solved with the help of synchronization. Synchronization is always a pain in most of the programming languages. But Java having language level threading and threading related constructs support, it made synchronization a breeze. There are different mechanisms present in Java to provide synchronization between threads. A synchronized section of code can be accessed by only one thread at a time. Other threads will wait until one thread completes running some specified section of code.
The synchronized Keyword
The keyword synchronized is available from the time threading is available in Java, if I am not mistaken somehow. It is the easiest way of synchronizing threads. The keyword synchronized can be used on a static or instance method or on some lines of code inside other static or instance methods. For using on whole method the keyword is used in method declaration. The lock object or the mutex that is used internally during this synchronization is the instance object or the class object. If you want to protect certain lines of code then you have to surround them with a synchronized block. You have to pass a non-null object as an argument to synchronized.
Code with synchronized
To get started with coding you need nothing special except JDK installed on your system. You can use any plain text editor, code editor or IDE of your choice. Create a project to start coding. Create a public class with main method called JavaSync or any other name you prefer. Our initial code should look like the following:
public class JavaSync {
public static void main(String[] args){
}
}
Create two other classes that extend Thread. Let's call one class T1 and the other T2. Create another class named Critical.
Critical
public class Critical {
private static int count = 0;
public static int getCount(){
return count;
}
public static void increaseCount(){
int c = getCount();
// For better demonstration, we are going to sleep here
try{
Thread.sleep(300);
}catch (Exception ex){
ex.printStackTrace();
}
count = c + 1;
}
}
T1
public class T1 extends Thread{
public void run(){
int c;
c = Critical.getCount();
System.out.println("T1: The value of count was read as: " + c);
// At this place the thread will do some work.
// We are sleeping for 200ms instead of doing some practical work.
try{
Thread.sleep(200);
} catch (Exception ex){
ex.printStackTrace();
}
Critical.increaseCount();
System.out.println("T1: The value of count was written as: " + Critical.getCount());
}
}
T2
public class T2 extends Thread{
public void run(){
int c;
c = Critical.getCount();
System.out.println("T2: The value of count was read as: " + c);
// At this place the thread will do some work.
// We are sleeping for 100ms instead of doing some practical work
try{
Thread.sleep(100);
} catch (Exception ex){
ex.printStackTrace();
}
Critical.increaseCount();
System.out.println("T2: The value of count was written as: " + Critical.getCount());
}
}
Now back to the main function:
public class JavaSync {
public static void main(String[] args){
int c = Critical.getCount();
System.out.println("Main: The initial count of Critical is: " + c);
Thread t1 = new T1();
Thread t2 = new T2();
t1.start();
t2.start();
try{
Thread.sleep(3000);
} catch (Exception ex){
ex.printStackTrace();
}
System.out.println("Main: The final count should be: " + (c+2) +" and actually is: " + Critical.getCount());
}
}
The code is self explanatory with its System.out.println() and comments along with previous discussion.
Now hit compile and run to see results similar to the following:
Main: The initial count of Critical is: 0
T1: The value of count was read as: 0
T2: The value of count was read as: 0
T2: The value of count was written as: 1
T1: The value of count was written as: 1
Main: The final count should be: 2 and actually is: 1
As the tasks were not synchronized we have an improper count value. We need to to synchronize the increaseCount() method. To sync a method among threads you need to put the synchronized keyword in the method declaration.
public class Critical {
private static int count = 0;
public static int getCount(){
return count;
}
public synchronized static void increaseCount(){
int c = getCount();
// For better demonstration, we are going to sleep here
try{
Thread.sleep(300);
}catch (Exception ex){
ex.printStackTrace();
}
count = c + 1;
}
}
Hit compile and run to see the following result.
Main: The initial count of Critical is: 0
T1: The value of count was read as: 0
T2: The value of count was read as: 0
T2: The value of count was written as: 1
T1: The value of count was written as: 2
Main: The final count should be: 2 and actually is: 2
Now, we have the expected result everywhere. We controlled the access of threads to a method with the addition of only one keyword. That's the sweetness of Java.
Ways of Using synchronized
You can use the keyword synchronized in the declaration of a static method or an instance method. You cannot use it on static fields or instance fields.
The keyword can be used to wrap an specific area of code inside an instance or static method.
In the Critical class:
public static void increaseCount(){
synchronized(Critical.class){
int c = getCount();
// For better demonstration, we are going to sleep here
try{
Thread.sleep(300);
}catch (Exception ex){
ex.printStackTrace();
}
count = c + 1;
}
}
Hit compile and run to see the same result. You have to pass an object to the keyword block on which it can lock on. I have provided the class object. If it was an instance method I would have provided this as the parameter. You can use different types of objects depending on your use case. But be careful to use the object. It should ensure that no other threads already have acquired lock on it. For class methods, the wisest choice is the class object and for instance method the wisest choice is the this object.
Conclusion
The synchronized keyword is a very powerful tool in Java to work with threads. But, as you know, with great power comes great responsibility. In this case great power comes with great danger. Use the tool with caution. Over use of it will slow down your program. Sometimes the performance penalty might go so high that it would be better to use a single thread. Use it with caution so that your program does not get locked forever.
More Java? Check out Processing ZIP Files with Java.
Be sure to stop by the homepage to search and compare SDKs, Dev Tools, and Libraries.
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment