DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • How Java Apps Litter Beyond the Heap
  • Java: How Object Reuse Can Reduce Latency and Improve Performance
  • How to Configure, Customize, and Use Ballerina Logs
  • Java Thread Synchronization and Concurrency Part 1

Trending

  • Secure by Design: Modernizing Authentication With Centralized Access and Adaptive Signals
  • Kullback–Leibler Divergence: Theory, Applications, and Implications
  • 5 Subtle Indicators Your Development Environment Is Under Siege
  • Event-Driven Architectures: Designing Scalable and Resilient Cloud Solutions
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. How Synchronization Works in Java (Part 1)

How Synchronization Works in Java (Part 1)

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

By 
Abdul Mohsin user avatar
Abdul Mohsin
·
Feb. 25, 18 · Tutorial
Likes (31)
Comment
Save
Tweet
Share
38.6K Views

Join the DZone community and get the full member experience.

Join For Free

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.

Java (programming language) Threading application Debug (command) Blocks

Opinions expressed by DZone contributors are their own.

Related

  • How Java Apps Litter Beyond the Heap
  • Java: How Object Reuse Can Reduce Latency and Improve Performance
  • How to Configure, Customize, and Use Ballerina Logs
  • Java Thread Synchronization and Concurrency Part 1

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: