How Synchronization Works in Java (Part 2)
This lesson in Java synchronization tackles synchronized methods, rather than blocks, and how synchronized operations work.
Join the DZone community and get the full member experience.
Join For FreeIn my previous article, we saw how synchronized blocks execute in Java. We did some debugging in Eclipse IDE as well to understand the flow of execution clearly.
This article focuses more on synchronized methods in Java, so let's begin.
Open your Customer class and see what actually the customer was doing, the code would be like:
public class Customer implements Runnable {
@Override
public void run() {
Bank bank = Bank.getInstance();
BankAccount account = bank.getAccount(123456);
synchronized(account) {
account.deposit(100);
account.withdraw(200);
}
}
}
The problem with this design is that the customer itself is acquiring the lock on the bankAccount Object. Ideally, the customer should not be aware of any locking, The Bank Account Class should be written in a way that, whenever a thread does some operation on it, it acquires the lock on this account. Let's do it by making the methods of BankAccount class synchronized.
Making Methods Synchronized
First, let's make those methods synchronized, as shown below:
public synchronized void deposit(Integer amount) {
// code
}
public synchronized Integer withdraw(Integer amount) {
// code
}
Making a method synchronized means that when a thread calls this method on an object of this class, then the thread will acquire the lock for that object on which the method has been called. For example:
BankAccount account = bank.getAccount(123456);
account.deposit(100);
Because the method deposit is synchronized, the above code is equal to the following:
BankAccount account = bank.getAccount(123456);
synchronized (account) {
account.deposit(100);
}
So, if more than one thread has a reference to the same object of the BankAccount class, and all of them try to call withdraw/deposit at the same time on that object, only one thread will be executed once at a time.
Sometimes, instead of synchronizing the complete method, we just synchronize the sensitive block like below:
public synchronized void deposit(Integer amount) {
synchronized (this) {
balance = balance + amount;
}
// other stuff
}
In this case as well, the calling thread will lock the object of this class (this) on which the method has been called.
Let's Analyze
In the BankAccount class, write the following two methods:
public synchronized void deposit(Integer amount) {
balance = balance + amount;
System.out.println(amount + " deposited by : " + Thread.currentThread().getName() + ", updated balance: " + balance);
}
public synchronized Integer withdraw(Integer amount) {
System.out.println(Thread.currentThread().getName() + " is trying to withdraw amount: " + amount + " from account: " + accountNumber);
if (amount > balance) {
System.out.println("not enough balance in account: " + accountNumber + " to withdraw");
System.out.println("returning zero to: " + Thread.currentThread().getName());
return 0;
}
this.balance = this.balance - amount;
System.out.println(Thread.currentThread().getName() + " successfully withdrow aomunt: " + amount + " from account: " + accountNumber);
System.out.println("updated balance: " + balance);
return amount;
}
And let our Customer class be the following:
@Override
public void run() {
Bank bank = Bank.getInstance();
BankAccount account = bank.getAccount(123456);
account.deposit(100);
account.withdraw(200);
}
And the main class is as it was before:
public static void main(String[] args) {
Customer customer1 = new Customer();
Customer customer2 = new Customer();
Thread thread1 = new Thread(customer1);
Thread thread2 = new Thread(customer2);
thread1.setName("Customer-1");
thread2.setName("Customer-2");
thread1.start();
thread2.start();
}
And put the breakpoint in the customer class as below:
Then, start the application in debug mode. Open the debug perspective in Eclipse — the screen will look something like:
You can see in the Debug View (top left) that both threads (Customer1 and Customer2) are suspended at line 9 in the Customer class. Now select Customer-1 thread and press F5:
You saw that Customer1 entered into the BankAccount deposit method, and also now it owns BankAccount(id=21). That means it has acquired the lock on the BankAccount object. Now, let's select the Customer2 thread and see whether it will go inside the deposit method at the same time. Select Customer2 and press F5:
You can see the Customer2 thread didn't enter into the deposit method. Instead, its state became stepping, which means it is blocked and now waiting for the lock to be released by the Customer1 thread.
The flow continues the same as it did in the previous article, so the lesson here is that only one thread can execute a synchronized method on an object.
So, what will happen if both threads have different objects? Suppose Customer1 has BankAccount Object-1, and Customer-2 has BankAccount Object-2. Will they be able to deposit into both accounts at the same time? They must be able to deposit into both accounts at the same time because Account-1 doesn't have to do anything will Account-2. You can analyze it with the following example:
Write this method inside the Bank Class:
public BankAccount openNewAccount() {
return new BankAccount(123456);
}
Change the Customer class as follows:
@Override
public void run() {
Bank bank = Bank.getInstance();
BankAccount account = bank.openNewAccount();
account.deposit(100);
account.withdraw(200);
}
Now what will the customer do? It opens a new account and deposits into it, then tries to withdraw from the same account. Now run the main class in debug mode. Let the breakpoint be at the same position.
Initially, both threads are suspended at line 9. Now select Customer-2 and press F5:
The Customer-2 thread entered into the deposit method and locked the BankAccount object (id=21). Now select Customer-1 and press F5:
This is what we were expecting! Customer-1 also entered into the deposit method and locked the BankAccount object (id=29). That means both threads acquired locks on different objects and entered into synchronized methods.
So the lesson remains the same — only one thread can do synchronized operations on an object. If multiple threads have different objects, then all of them can perform synchronized operations on those objects.
Opinions expressed by DZone contributors are their own.
Comments