Java Concurrency: Threads communication tutorial
Tags: java, beginners, webdev, programming
Published: 2022-09-07 05:08:42

## Overview
Java provides mechanisms for threads to communicate with each other when working on common resources. This post gives you a concrete example of threads communication.
## Threads communication example
Let's consider this scenario. Alice is a computer programmer who also has a love for buying new shiny things (phones, laptops, gadgets). She has a list of things to buy. She works at a company called MonkeyTypes Inc. Her paycheck is $1,000 monthly.
Imagine she currently has this wishlist:
- A new Macbook: $3000
- A new mechanical keyboard: $400,
- A new phone: $500,
- A new shiny gadget: $500

She wants to have all of them in no particular order.
Her current balance is $0. What she wants is when she gets her monthly salary, she would spend the money in her balance to buy any of those things.
Let's see how we can convert this scenario to demonstrate thread communication.

## Code implementation
For this scenario, let's create two Runnable tasks: One to simulate Alice paycheck and other to simulate her purchase.
We also need a class to represent her bank account.
Let's create the bank account class first:
```java
class BankAccount {
    private int balance = 0;


    private static Lock lock = new ReentrantLock();
    private static Condition paycheckArrivedCondition = lock.newCondition();

    public void getPaid(int amount) {
        lock.lock();
        try {
            System.out.println("Getting paid " + amount);
            balance += amount;
            paycheckArrivedCondition.signalAll();
        } finally {
            lock.unlock();
        }
    }


    public void withdraw(int amount, String purpose) {
        lock.lock();
        try {
            while (balance < amount) {
                paycheckArrivedCondition.await();
            }
            System.out.println("Withdraw " + amount + " to " + purpose);
            balance -= amount;

            System.out.println("new balance -> " + balance);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
```
As you can see, this class has one field: `balance` to hold the current balance. Also, there are two methods to deposit and withdraw money to and from the balance.
The most interesting details here are the lock and the condition. I created a static lock and a static condition at the beginning of the class. The lock, as you may know, helps synchronize access to the balance. The condition, on the other hand, helps make communication between threads possible.
### The withdraw() method
At the beginning of the `withdraw` method, the `lock` method on the ReentrantLock instance is called. This ensures that only the thread that has the lock can execute the code in this function.
Next, the try/catch/finally blocks make sure the lock is released at the end.
The while loop checks if the balance has enough money, if not, the `await` function is called on the condition instance.** This call releases the lock.**
### The deposit() method
Similar to the `withdraw()` method, threads need to acquire the lock to execute code here. One interesting thing about this method is the call to the method `signalAll `on the condition instance. This call is the meat of thread communication. This wakes up all the waiting threads and the check for balance &gt; amount starts again.
## The Runnable class to deposit money
Now the BankAccount class is available, let's create a runnable class to deposit money to a BankAccount instance:
```java
class PayEmployee implements Runnable {

    private final BankAccount bankAccount;
    private final int amount;

    PayEmployee(BankAccount employeeBankAccount, int amount) {
        this.bankAccount = employeeBankAccount;
        this.amount = amount;
    }

    @Override
    public void run() {
        bankAccount.getPaid(amount);
    }
}
```

## The Runnable class to withdraw money
```java
class BuyThings implements Runnable {
    private final BankAccount bankAccount;
    private final String purpose;

    private final int amount;

    public BuyThings(BankAccount account, String purpose, int amount) {
        this.bankAccount = account;
        this.purpose = purpose;
        this.amount = amount;
        System.out.println("Plan to " + purpose + " with " + amount);
    }

    @Override
    public void run() {
        bankAccount.withdraw(amount, purpose);
    }
}
```
## Alice buys things in action
Now let's implement the code where Alice submits her wishlist.
```java
public static void main(String[] args) {
        BankAccount myAccount = new BankAccount();
        var executors = Executors.newFixedThreadPool(5);
        executors.submit(new BuyThings(myAccount, "buy new macbook pro", 3_000));
        executors.submit(new BuyThings(myAccount, "buy new phone", 500));
        executors.submit(new BuyThings(myAccount, "buy new keyboard", 400));
        executors.submit(new BuyThings(myAccount, "buy new gadgets", 500));

        int cycle = 6;
        while (cycle > 0) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException ex) {
               ex.printStackTrace();
            }

            executors.submit(new PayEmployee(myAccount, 1_000));
            cycle--;
        }

        executors.shutdown();
    }
```
From line 4 to line 7, all her purchases are submitted.
From line 9 to line 19, I simulate her payment. Let's say her contract with the company has only 6 months left. Let's run the program and see the output:
![Thread communication in action](https://datmt.com/wp-content/uploads/2022/09/image-2.png)
As you can see, after getting paid the first $1000, Alice buys a phone and then a new keyboard ... However, this order is not consistent. The next run may produce different order. One certain thing is a MacBook always gets purchased last because only after the next-to-final payment, Alice have enough money to afford this.
![Different purchasing order](https://datmt.com/wp-content/uploads/2022/09/image-3.png)
You may ask, what if Alice only has 4 payment cycles left instead of 6? That means she never has enough money for a MacBook. In such a case, the program will run forever because the buy Macbook thread keeps waiting for the condition to meet. (so sad :( )
## Conclusion
In this post, I have introduced you to the concept of thread communication in Java using Lock and condition. A thread can acquire the lock, and check if the condition is met. If the condition is not met, a call to `await` on the condition instance releases the lock for other threads. A thread can notify all other threads by using `signalAll` (or `signal` to notify a random thread).
The code for this post is available here on <a href="https://github.com/datmt/java-core-tutorial/blob/main/src/com/datmt/java_core/concurrency/ThreadCommunication.java" target="_blank" rel="noreferrer noopener nofollow">Github</a>

This post is a part of the <a href="https://datmt.com/series/java-concurrency/">Java concurrency tutorial series</a>