Synchronizing Threads in Java: A Practical Guide
Synchronization in java is used to remove the data inconsistency when multiple threads access the same object or resource simultaneously. In this article, we will briefly discuss different types of thread synchronization in Java.
In this article, we will briefly discuss the synchronization of Thread in Java with the help of examples. But before going deeper to know more about synchronization of thread, you must have a clear understanding of threads and multithreading in Java.
When multiple threads access the same object simultaneously, then there may be data inconsistency. To resolve this data inconsistency problem synchronized method or synchronized block should be used. If a method or block is synchronized, then only one thread at a time is allowed to execute that method or block on a given object. This is what thread synchronization in java means.
Must Check: What is Java?
Must Check: Top Java Online Courses & Certifications
Types of Thread Synchronization in Java
There are two types of Thread synchronization in Java:
Now, letโs discuss them, one by one in a complete detail:
Best-suited Java courses for you
Learn Java with these high-rated online courses
Mutual Exclusive
Method level Synchronization
Suppose multiple threads operate on the same method, and that method is not synchronized. In that case, a data inconsistency problem will occur, so to overcome this problem, we make a synchronized method.
Syntax
datatype synchronized method
void synchronized print(){ // method is synchronized // lines of codes
}
With the help of a program, letโs look at what challenges we will face when the method is not synchronized.
Example:
In the below program, class Table contains one method print() which is not synchronized. Two threads, t1 and t2 are accessing the same print() method, which print table 4 and 6, respectively. Letโs see the output we get from the program when the method is not synchronized.
Code
class Table{ void print(int val){ // method is not synchronized // In this for loop , table of 4 and 6 is print.
for(int i=1;i<=10;i++){ System.out.println(val*i); try{ Thread.sleep(1000); }catch(Exception e){ System.out.println(e); } } }}//Thread 1 access print() method
class Thread1 extends Thread{ Table t; Thread1(Table t){ this.t=t; } public void run(){
// access print() method of Table class and pass 4 as argument
t.print(4); }}
//Thread2 access print() method
class Thread2 extends Thread{ Table t; Thread2(Table t){ this.t=t; } public void run(){
// access print() method of Table class and pass 6 as argument t.print(6); }}
public class Main{ public static void main(String[] args) { Table t=new Table(); Thread1 t1=new Thread1(t); Thread2 t2=new Thread2(t); t1.start(); t2.start(); }}
In the above program, class Thread1 and Thread2 extends the Thread class. In Thread1 class, the print() method of Table class is called to print table of 4 and in the Thread2 class, the print() method of the Table class is called to print a table of 6. Both classes access the print() method simultaneously. Since the method is not synchronized, we get different outputs.
Output
Here, by observing the outputs, we conclude that if the method is not synchronized, then we get different outputs, or a data inconsistency problem will occur. So, to resolve this issue, we have to make a synchronized method. Now letโs make the method synchronized and then see the output.
Code
class Table{ synchronized void print(int val){ // method is synchronized
// In this for loop , table of 4 and 6 is print.
for(int i=1;i<=10;i++){ System.out.println(val*i); try{ Thread.sleep(1000); }catch(Exception e){ System.out.println(e); } } }}
//Thread 1 access print() method
class Thread1 extends Thread{ Table t; Thread1(Table t){ this.t=t; } public void run(){
// access print() method of Table class and pass 4 as argument
t.print(4); }}
//Thread2 access print() method
class Thread2 extends Thread{ Table t; Thread2(Table t){ this.t=t; } public void run(){
// access print() method of Table class and pass 4 as argument
t.print(6); }}
public class Main{ public static void main(String[] args) { Table t=new Table(); Thread1 t1=new Thread1(t); Thread2 t2=new Thread2(t); t1.start(); t2.start(); }}
Output:
Here, the print() method is synchronized, so at a time, only one thread is allowed to execute the method. Hence, we get the same output, and thus, we solve the problem of data inconsistency that we saw in the earlier program when the method is not synchronized. The first table of 4 is printed, and then the table of 6 will print.
Block Level Synchronization
In block-level synchronization, the entire method is not synchronized; only the part of the method or the block of code in the method is synchronized, which allows one thread to execute that block of code at a time.
Syntax
synchronized(this){ //lines of code}
Also Read: This Keyword in Java
Example:
Letโs take the same example again of table printing, but instead of making the method synchronized, we make a synchronized block inside the print () method to print tables 4 and 6. We always get the same output as the thread is synchronized, and at a time, one thread enters inside the synchronized block to execute the code.
Code:
class Table{ void print(int val){ synchronized(this){ // block is synchronized for(int i=1;i<=10;i++){ System.out.println(val*i); try{ Thread.sleep(1000); }catch(Exception e){ System.out.println(e); } } } }}
//Thread 1 access print() method
class Thread1 extends Thread{ Table t; Thread1(Table t){ this.t=t; } public void run(){ t.print(4); }}
//Thread2 access print() method
class Thread2 extends Thread{ Table t; Thread2(Table t){ this.t=t; } public void run(){ t.print(6); }}
public class Main{ public static void main(String[] args) { Table t=new Table(); Thread1 t1=new Thread1(t); Thread2 t2=new Thread2(t); t1.start(); t2.start(); }}
Here, we also get the same output that we get in the synchronized method.
Output:
Static Synchronization of Thread
As we know, in Java, each object has one lock, which means at a time, one thread can execute a block of code on a given instance of the class. So, if we have multiple objects in our program, then multiple locks are there, and if we are dealing with multiple threads in our program, then the output may be corrupt, and a data inconsistency problem will occur. To resolve such a problem, we use a static synchronized block or method.
Example
Letโs assume Shubham, Vikram, Inder, and Kamlesh have a joint account in a bank. The total balance in the bank account is 10000 rupees. If Shubham or Vikram withdraws 10000, then the amount withdrawn by any of the rest is zero or insufficient balance is shown to them, but Inder or Kamlesh is still able to withdraw 10000 rupees, and vice-versa, this shows the program is incorrect and inconsistent or corrupt data is given by the program.
Go through the below program to get clarity on the above mentioned example.
In the below program, two objects are created b1 and b2. For one object, one lock is there, which means at a time one thread is allowed to execute the method, so, for two objects, two locks are there, which allow two threads to execute the same method simultaneously; hence we get corrupt output, or 20000 amount is withdrawn in spite of having only 10000 amounts in Bank.
Code
class Bank extends Thread{ int available_balance=10000; int withdrawl_balance; Bank(int withdrawl_balance){ this.withdrawl_balance=withdrawl_balance; } public synchronized void withdraw(){ // method is synchronized
String thread_name=currentThread().getName(); if(withdrawl_balance<=available_balance){ System.out.println(thread_name+""+" withdraw money:"+""+withdrawl_balance); available_balance-=withdrawl_balance; }else{ System.out.println(thread_name+" your account has Insufficient balance"); } } public void run(){ withdraw(); } }
public class Main{ public static void main(String[] args) { Bank b1=new Bank(10000); // has one lock
Thread t1=new Thread(b1); t1.setName("Shubham"); Thread t2=new Thread(b1); t2.setName("Vikram"); t1.start(); t2.start(); Bank b2=new Bank(10000);// has one lock
Thread t3=new Thread(b2); Thread t4=new Thread(b2); t3.setName("Inder"); t4.setName("Kamlesh"); t3.start(); t4.start(); }}
Output:
Now, if we make method static synchronized then one thread execute method at a time and we get consistent data.
Code:
class Bank extends Thread{ static int available_balance=10000; static int withdrawl_balance; Bank(int withdrawl_balance){ this.withdrawl_balance=withdrawl_balance; } public static synchronized void withdraw(){ // method is static synchronized
String thread_name=currentThread().getName(); if(withdrawl_balance<=available_balance){ System.out.println(thread_name+""+" withdraw money:"+""+withdrawl_balance); available_balance-=withdrawl_balance; }else{ System.out.println(thread_name+" your account has Insufficient balance"); } } public void run(){ withdraw(); } }
public class Main{ public static void main(String[] args) { Bank b1=new Bank(10000); // has one lock
Thread t1=new Thread(b1); t1.setName("Shubham"); Thread t2=new Thread(b1); t2.setName("Vikram"); t1.start(); t2.start(); Bank b2=new Bank(10000);// has one lock
Thread t3=new Thread(b2); Thread t4=new Thread(b2); t3.setName("Inder"); t4.setName("Kamlesh"); t3.start(); t4.start(); }}
Output:
Inter-Thread Communication
Two threads can communicate with each other by using wait(), notify(), and notifyall() methods. The thread which is expecting updation is responsible for calling the wait() method and then immediately entering into the waiting state. The thread which is responsible for performing the updation call notify() method so that the waiting thread gets a notification and can continue its execution with those updated items.
Example:
In the below program, the Main thread communicates with the child thread to print the sum of the first ten natural numbers. Here main thread expects an updation of the sum, and the child thread performs an updation. Also note that wait(), notify(), and notifyall() methods must be put inside the synchronized block; otherwise, the program gets IllegalMonitorStateException.
Code:
class Thread1 extends Thread{ int sum=0; public void run(){ synchronized(this){ for(int i=1;i<=10;i++){ sum+=i; } //child thread send notification to Main thread this.notify(); } }}public class Main{ public static void main(String[] args) throws Exception { Thread1 t=new Thread1(); t.start(); synchronized(t){ //Main thread calling wait() method t.wait(); //Main Thread get notification System.out.println(t.sum); } }}
Output
Conclusion
In this article, we have discussed:
- What is Synchronizing thread in Java?
- Types of Synchronizing thread with examples
Hope you will like the article.
Contributed By: Shubham Kumar
This is a collection of insightful articles from domain experts in the fields of Cloud Computing, DevOps, AWS, Data Science, Machine Learning, AI, and Natural Language Processing. The range of topics caters to upski... Read Full Bio