To enhance the speed of a program, we use multiple threads to access the same resource at the same time. But, this kind of shared resources can produce unexpected results due to this simultaneous access made by multiple threads. The following example demonstrates the problem.
Here, we have a sample bank system that contains 4 classes which are responsible for updating the account balance of a particular customer. The task is to update the account by 10 dollars for 100 times. To accomplish the task we created a cached thread pool which creates 100 threads when executing it.
The Account class contains the balance of the particular customer whereas the Deposit class is responsible for updating the account balance by a given amount. The Runnable interface is implemented by the DepositAccount class. AccountManager class is responsible for starting the program since it contains the main method.
Account class
public class Account {
private static double balance = 0;
public static double getBalance() {
return balance;
}
public static void setBalance(double updateBalance){
balance = updateBalance;
}
}
Deposit class
public class Deposit {
public void deposit(double amount) throws InterruptedException {
double temp = Account.getBalance();
Thread.sleep(5);
Account.setBalance(temp+amount);
}
}
DepositAccount class
public class DepositAccount implements Runnable {
Deposit updateAccount;
public DepositAccount(Deposit updateAccount){
this.updateAccount = updateAccount;
}
@Override
public void run() {
try {
updateAccount.deposit(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
AccountManager class
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class AccountManager {
public static void main(String[] args) {
Deposit deposit = new Deposit();
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
for(int i = 0; i<100; i++){
DepositAccount depositAccount = new DepositAccount(deposit);
executor.execute(depositAccount);
}
executor.shutdown();
while(!executor.isTerminated()){
}
System.out.println(Account.getBalance());
}
}
//Output one
70.0
//Output two
110.0
The program creates 100 threads executed in a thread pool executor. The isTerminated() method is used to test whether the thread is terminated. The balance of the account is initially 0. When all the threads are finished, the balance should be 1000 but the output is unpredictable. As can be seen in the output, the answers are wrong in the sample run. This demonstrates the data corruption problem that occurs when all the threads have access to the same data source simultaneously.
In this case, instead of adding thread by thread(10 by 10) to the balance most of the threads are accessing the resource simultaneously, cause to use the initial balance as 0. so all those simultaneous threads will update the balance from 0 to 10. Because of the time delay, the other set of threads will update the balance from 10 to 20. The value is raised up-to some level but not the correct level.
To get the correct result, we should create some mechanism for granting the shared resource for one thread at a time. In java, this mechanism is known as Synchronization.
Thread synchronization is mainly used to overcome interference between multiple threads and
to prevent thread consistency problems.
Java provides synchronization for threads. Java synchronization is the best option to use where we need to give access only for one thread at a time for a shared resource.
Java provides three mechanism to prevent thread interference.
- Synchronized methods
- Synchronized blocks
- static synchronization
Java synchronization is build using a lock. A thread that needs consistent access to an object’s fields has to acquire the object’s lock before accessing them.
Synchronized methods
In Java synchronized methods mechanism, you just need to type the keyword synchronized on the method that you need to lock when a thread is executing, this lock will be released as soon as the thread completes its task.
To solve the thread interference problem in the above program, we can update the Deposit class as shown below.
public class Deposit {
public synchronized void deposit(double amount) throws InterruptedException {
double temp = Account.getBalance();
Thread.sleep(5);
Account.setBalance(temp+amount);
}
}
so, here we have added the synchronized keyword on the method signature, that helps to create this method as a synchronized method. Once you have done that, you will get the correct answer as 1000.
Java Synchronized blocks.
There are some times that you do not need to create a synchronized method but you do need to create a synchronized calling statement to a method, in that way we can synchronize a particular execution of a method other than the whole method. With synchronized blocks, you can synchronize a part of a method or calling statement to a method.
Here, we can synchronize the calling statement to the deposit method instead of synchronizing the deposit method.
public class DepositAccount implements Runnable {
Deposit updateAccount;
public DepositAccount(Deposit updateAccount){
this.updateAccount = updateAccount;
}
@Override
public void run() {
synchronized (updateAccount) {
try {
updateAccount.deposit(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Java Static Synchronization
This is about making a synchronized method static. As we already know, method synchronization mechanism is used to lock a method during its execution by a thread. This lock is applied to a particular object. When two objects are accessing a method, it produces a wrong result even though it is a synchronized method. The reason is two different objects make two different locks on the method. So, they work independently and produces the wrong result.
By making the Synchronized method static. We can create a same lock for all the objects.
Suppose our AccountManager class is as follows.
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class AccountManager {
public static void main(String[] args) {
Deposit depositOne = new Deposit();
Deposit depositTwo = new Deposit();
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
for(int i = 0; i<50; i++){
DepositAccount depositAccount = new DepositAccount(depositOne);
executor.execute(depositAccount);
}
for(int i = 0; i<50; i++){
DepositAccount depositAccount = new DepositAccount(depositTwo);
executor.execute(depositAccount);
}
executor.shutdown();
while(!executor.isTerminated()){
}
System.out.println(Account.getBalance());
}
}
Now, here two objects are used (depositOne and depositTwo) to access the deposit class.
This program generates wrong results even though the deposit method is synchronized.
To overcome this problem, we should make the deposit method a static synchronized method as follows.
public class Deposit {
public static synchronized void deposit(double amount) throws InterruptedException {
double temp = Account.getBalance();
Thread.sleep(5);
Account.setBalance(temp+amount);
}
}
NOTE| up-to-now we just created threads only implementing the Runnable class or extending the Thread class. In both cases, we override the run method, but there is a problem with this run method. It does not return anything. Now you can create threads with returning statement through the callable interface. let’s briefly discuss what is callable with another post.