Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How Synchronization Works in Java (Part 1)

DZone's Guide to

How Synchronization Works in Java (Part 1)

This introductory primer to Java synchronization will discuss how synchronization solves problems and introduces locks.

· Java Zone ·
Free Resource

Get the Edge with a Professional Java IDE. 30-day free trial.

If you've decided to learn synchronization in Java, then let's start without wasting time. This is the first part of a series of articles on synchronization.

Definition

Synchronization means to control the access of multiple threads to a shared resource.

How Is It Done?

For practice purposes, I would recommend having Eclipse installed on your PC/laptop. This will help you to understand the threads and their states more easily.

  • Open your Eclipse

  • Go to File -> New -> Java Project

Image title
Give it a name and click finish.

  • A project will be created in your workspace

  • Now in your project--> src folder, right click on it

  • Select New -> Package

Image title

  • Give it a name and click finish. The package will be created inside your src folder

  • Right-click on your package, select New -> Class

  • Enter class name as "Main"

Image title

  • Tick the checkbox "public static void main"

  • Click finish

The main class has been created!

Now we will create a basic banking application in which 2 customers (Threads) will try to deposit/withdraw an amount from a single bank account

Create the class Bank as follows:

public class Bank {

private static Bank instance = new Bank();
private HashMap<Integer, BankAccount> accountNumberVsAccount;

    private Bank() {
    accountNumberVsAccount = new HashMap<Integer, BankAccount>();
    accountNumberVsAccount.put(123456, new BankAccount(123456));
    }

    public static Bank getInstance(){
        return instance;
    }

    public BankAccount getAccount(Integer accountNumber) {
        return accountNumberVsAccount.get(accountNumber);
    }
}


The Bank class contains a map of accountNumberVsAccount.

It has a method getAccount, which simply returns the BankAccount instance of the account number.

Now we will create the BankAccount Class:

public class BankAccount {

      private Integer balance;
      private Integer accountNumber;

      public BankAccount(Integer accountNumber, Integer initialBalance) {
      this.accountNumber = accountNumber;
      balance = initialBalance;
      }

      public BankAccount(Integer accountNumber) {
      this(accountNumber, 0);
      }

      public Integer getBalance() {
      return balance;
      }

      public Integer getAccountNumber() {
      return accountNumber;
      }

      public void deposit(Integer amount) {
            balance = balance + amount;
            System.out.println(Thread.currentThread().getName() + " depositing the amount "+amount+" updated balance =  " + balance);
      }

  public Integer withdraw(Integer amount) {
    System.out.println(Thread.currentThread().getName() + " trying to withdraw " + amount + " from the account " + accountNumber);
    if (balance < amount) {
      System.out.println("OOPS, NO BALANCE LEFT TO WITHDRAW FOR "+Thread.currentThread().getName());
      return 0;
    }
    balance = balance - amount;
    System.out.println(Thread.currentThread().getName() + " successfully withdrow the amount. balance left =  " + balance);
    return amount;
  }
}


Let's understand this class.

The BankAccount contains its accountNumber and a balance, and two methods (withdraw and deposit) are there for updating the balance of the account. Also, if the balance is not enough, then the value 0 is returned with an error message.

Now let's create the Customer class (Runnable):

public class Customer implements Runnable {

    @Override
    public void run() {
        Bank bank = Bank.getInstance();
        BankAccount account = bank.getAccount(123456);
        account.deposit(100);
        account.withdraw(200);
    }
}


Let's see what the customer is doing. When it runs:

  1. It gets the  Bank instance

  2. It gets the BankAccount Object from the bank 

  3. It deposits  100 bucks to the account

  4. And finally, it withdraws 200 bucks from the account

  • Now, what can happen if 2 customers (2 instances of the Customer class) run at the same time? Both will get the SAME  BankAccount Object from the Bank.

  • Then both will deposit 100 bucks to the account. The balance should be 200.

  • Now both customers will try to withdraw 200, but only one customer should be able to withdraw 200 (think it as in a real-world scenario).

  • Because no balance is left in that account, another customer should not be able to withdraw the amount

Now we will write the code for main class and produce the same scenario stated above

public class Main {

    public static void main(String[] args) {
        Customer customer1 = new Customer();
        Customer customer2 = new Customer();
        Thread t1 = new Thread(customer1);
        Thread t2 = new Thread(customer2);
        t1.setName("Customer-1");
        t2.setName("Customer-2");
        t1.start();
        t2.start();
    }
}


Now Run the main class 3-4 times and analyze the inconsistent output. You should see that the behavior is not consistent. Sometimes, one customer is able to withdraw, and sometimes, both customers are not able to withdraw. Ideally, every time, one customer should be able to withdraw the amount from the account. But that is not what is happening. This is the problem, and that is where synchronization comes into the picture.

To make this behavior consistent and to make it work as expected, we need to synchronize the BankAccount object before doing any operations (deposit/withdraw) on it.

To synchronize an object, we need to write:

synchronized (object) {
    // do some operations on obj
}


So in our customer class, we will make the following changes:

@Override
public void run() {
    Bank bank = Bank.getInstance();
    BankAccount account = bank.getAccount(123456);

    // obtain a lock on the account before performing operations
    synchronized (account) {
        account.deposit(100);
        account.withdraw(200);
    }
}


This means that the current thread (Customer) needs to obtain a lock on the account object before doing any operations on it.

Try running the application now: Every time, the output will be correct — one customer will be able to withdraw the amount each time.

How it Happened

For a deeper understanding it, let's debug the application in Eclipse. Put the breakpoint in the Customer class as shown:

Image title

Now, right click on the main class and select debug as -> Java application

The application will start in debug mode, and the Debug perspective will be opened as below:

Image title

See the debug view in the top left corner. This shows the current running threads in our application. (See the highlighted lines below):

Image title

You can select a thread by clicking on it, and then resume it, stop it etc.

Suppose I select thread customer-1 and press F6. The thread will go into a synchronized block. See the debug view now:

Image title

A new line appeared that says:

owns: BankAccount (id=29)

This means that this thread (Customer-1) has obtained a lock on that object of BankAccount, so if another thread tries to obtain the lock on that object, the thread will get blocked. Let's try it.

Select the thread Customer-2 (click on it) and then press F6. Did it go into the synchronized block? See the debug view:

Image title

The answer is no, it did not enter into a synchronized block (still on line 9). Its state became Stepping instead of Suspended, which means it is blocked now. 

Now let's get customer-1 out of the synchronized block (by selecting it and pressing F6 3 times). It will leave the synchronized block — notice now that the Customer-2 thread instantly moved into the suspended state and obtained the lock (see in the debug view):

Image title

That means the Customer-2 thread entered into the synchronized block instantly after Customer-1 got out of the block.

REMEMBER: Only ONE thread at a time can execute a synchronized block on the SAME object.

Questions are welcome.

Get the Java IDE that understands code & makes developing enjoyable. Level up your code with IntelliJ IDEA. Download the free trial.

Topics:
java ,synchronization blocks ,locks ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}