Core Java

Java Wait Example

1. Introduction

In this article, we will work on an example to implement wait, notify, notifyAll in Java multithreaded environment.

Thread is a lightweight process within java process. Multithreading helps in maximizing CPU utilization. It allows concurrent execution of multiple parts of java program using threads. All java programs (multithreaded or not) by default, start within a thread named as main thread.

In this example, we are going to demonstrate how to enable communication between multiple threads that are using same resource/object.

2. Java Thread Basic Methods

2.1 join

public final void join() throws InterruptedException

Thread class provides join method to allow one thread to wait for another thread until another one completes execution. Calling this function puts the current thread on wait until the thread it is called upon, finishes execution.

This method throws InterruptedException if the thread is interrupted.

public final void join(long millis) throws InterruptedException

There is another variant of join method, which takes milliseconds as argument where current thread waits only for the given milliseconds for other thread to complete.

2.2 sleep

sleep method takes milliseconds as an argument and it puts current thread to sleep for the milliseconds passed to the method relinquishing any lock it is holding right now. If thread is interrupted while it has been put to sleep then the method throws InterruptedException exception.

public static void sleep(long millis) throws InterruptedException

2.3 yield

public static void yield()

yield method indicates that current thread can yield the control of the CPU and any other thread can take control of CPU, if required. Scheduler can ignore the indication or if there is no other thread to give control, then the current thread will continue running.

2.4 start

public void start()

Invoking start method on a thread object spawns a new thread in java process and start() method internally calls run() to execute the new created thread. If start method is invoked again then an IllegalStateException exception will be thrown.

2.5 run

public void run()

As mentioned above, start method invokes run method internally when a new thread is spawned. If we invoke run directly (without start() invocation) then it will not spawn new thread but run will execute in the stack of the current thread only like a regular method call.

3. Synchronization in Java

In a multithreaded environment, threads work on and manipulate shared resources. Synchronization in java provides mutual exclusive access of shared resource to threads. This helps in preserving the right state of the resource as well as prevents dirty read of the resource.

synchronized keyword provides lock of the object that ensures the mutually exclusive access and prevents race condition.

3.1 synchronized method

synchronized keyword when used with method indicates that the thread will get lock on the class object.

public synchronized void setValue(int value){
    this.value = value;
}

synchronized keyword when used with static method indicates, that the thread will get lock on class rather than object

public static synchronized int getValue(){
    return value;
}

3.2 synchronized block

synchronized block is same as synchronized method but sometimes we don’t want to block the whole method but only a section which manipulates shared resource.

synchronized block takes object as argument. Thread obtains lock on the object when it starts executing synchronized block and relinquishes it at exit of the synchronized block. Like, in double checked locking in case of Singleton object creation

public static SingletonClass getInstance(){
    if(instance == null){
        synchronized(SingletonClass.class){
            if(instance == null)
                instance = new SingletonClass()
        }
    }
    return instance;
}

There are other constructs available to acquire lock like using volatile, atomic variables or using Lock interface explicitly.

4. wait(), notify() and notifyAll()

Java wait(), notify() and notifyAll() all are defined in Object class which means any type of object in Java can invoke these method to wait or notify for a shared resource

4.1 wait

public final void wait() throws InterruptedException

When invoked, it causes current thread to go in waiting state for the object until another thread notifies it.

There are two other variants of wait method, which takes waiting time in milliseconds as argument. Thread waits for notification only till waiting time expires and then comes back in runnable state.

If thread is interrupted while waiting then the method throws InterruptedException exception.

4.2 notify

public final void notify()

notify method when invoked sends notification to one of the waiting threads to acquire lock of the shared resource. If multiple threads are waiting on same resource then, notify will send notification to one of them in no particular order.

4.3 notifyAll

public final void notifyAll()

notifyAll method is same as notify but notifyAll notifies all of the waiting threads unlike notify. Though only of the awakened thread will be able to acquire lock of resource, while other threads will go in wait again most probably or exit.

4.4 Thread States

Below diagram shows the lifecycle of the thread from its creation till the exit.

Java Wait - Lifecycle of a Thread
Lifecycle of a Thread

5. Example

We will now see a working example of how to use wait and notify to setup communication between multiple threads.

We will be working on a Producer-Consumer example here. Basically, Producer will produce an element and push it in a shared resource, an Arraylist in our case, while Consumer will consume an element from the shared resource.

Producer and Consumer will use wait and notify to inform other when it can continue.

5.1 Producer

Let’s first define our Producer. Here are few things to keep in mind while defining Producer:

  • It must implements Runnable, so new producer thread can be created. There is another option of extending Thread class, but we have not used that as Java classes can only extend from one class but can implement many interfaces, so this approach provides flexibility.
  • Producer need to have access to the shared resource i.e. sharedList in this case, which is defined as instance variable in Producer class.
  • We have defined a limit on list, maxCount, that how many elements it can hold before producer has to stop and wait for consumer to consume few elements from the list.
  • A constructor to initialize both sharedList and maxCount while creating thread.

Constructor of Producer to initialize shared resource and limit

class Producer implements  Runnable {

    List sharedList;
    int maxCount = 0;
    int elementCount = 0;

    public Producer(List sharedList, int maxCount) {
        this.sharedList = sharedList;
        this.maxCount = maxCount;
    }
...
}
  • Since Producer class implements Runnable interface, we need to provide an overridden definition of run method.
  • As mentioned above, run method contains the code that thread executes once it starts
  • run is invoked internally by start method and we won’t invoke run directly from code.
  • In below code, run invokes another local method produce which produces an element and adds it in shared resource sharedList, which we will see in a bit.

Invoking produce method from run at thread invocation

public void run() {
        try {
            produce();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void produce() throws InterruptedException {

    ....
    }

5.1.1 produce method

Let’s now see what produce method does, which is where actual action for Producer lies.

  • First of all our producer should not go down and keep on trying to insert produced data in sharedList, so we will run a while loop with true condition so that producer keeps on running.

While loop to keep producer running continuously

private void produce() throws InterruptedException {
        while(true) {
        ...
        }
}
  • Since sharedList is the shared resource and we don’t want threads to access sharedList at the same time. So all of the work will be done under a synchronized block only.

Synchronizing on shared resource

private void produce() throws InterruptedException {
        while(true) {
            synchronized (sharedList) {
            ...
            }
        }
}
  • Now if suppose, sharedList has reached its limit (remember maxCount we set?), we don’t want producer to produce any more but rather wait for consumer to consume few elements first.
  • So every time we loop over, we will first check if size() of the sharedList has reached maxCount. If so then producer will invoke wait in Java on sharedList and will go in wait until consumer consumes and notify.

Wait for Consumer to consumer data if shared resource has reached capacity

private void produce() throws InterruptedException {
        while(true) {
            synchronized (sharedList) {
                while (sharedList.size() == maxCount) {
                    sharedList.wait();
                }
            }
        }
}
  • In case limit has not reached and size of sharedList is still less than maxCount, then producer will add one element in the sharedList and notify any waiting consumer thread to consume from sharedList
  • We are adding elementCount in list incrementing it after adding to keep track of elements produced and consumed.

Method to push data into shared resource

private void produce() throws InterruptedException {
        while(true) {
            synchronized (sharedList) {
                while (sharedList.size() == maxCount) {
                    sharedList.wait();
                }

                while (sharedList.size() < maxCount) {
                    System.out.println(Thread.currentThread().getName()+ " producing: " + elementCount);
                    sharedList.add(elementCount++);
                    sharedList.notifyAll();
                }
            }
        }

    }

5.2 Consumer

We will define our Consumer class now. Here are few things to keep in mind while defining Consumer:

  • It must implements Runnable as well, since we want to run Consumer in a separate thread.
  • Consumer need to have access to the shared resource i.e. sharedList. Same has been defined as instance variable in Consumer class.
  • A constructor to initialize sharedList while creating thread.

Constructor of Consumer Class

class Consumer implements Runnable {
    List sharedList;

    public Consumer(List sharedList) {
        this.sharedList = sharedList;
    }
    ...
}
  • We will be implementing run method which calls consume method to consume element from sharedList

Invoking consume method from run at thread invocation

public void run() {
        try {
            consume();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void consume() throws InterruptedException {
    ...
    }

5.2.1 consume method

  • Like producer, we want our consumer thread to run infinitely so that it can keep consuming elements inserted in list. So there is a while loop with true condition to keep it running.
  • Similarly, before consuming anything consumer must take lock of sharedList to avoid simultaneous access of the list.

Synchronizing shared list

private void consume() throws InterruptedException {
        while(true) {
            synchronized (sharedList) {
                ...

            }
        }
    }
  • Now consumer can only consume when there is an element available in sharedList
  • To validate that, consumer will first check if the size of the sharedList is 0. If it is then, consumer will just wait on sharedList till producer adds a new element in list and notify consumer thread.
  • If size if not 0, which means there are elements in the sharedList, then consumer will remove the first element from list, print it and notify any thread (like producer) to continue working.

Method to consume data from shared resource

private void consume() throws InterruptedException {
        while(true) {
            synchronized (sharedList) {
                while (sharedList.size() == 0) {
                    sharedList.wait();
                }

                System.out.println(Thread.currentThread().getName()+ " consumed: " + sharedList.remove(0));
                sharedList.notifyAll();

            }
        }
    }

5.3 Start the Threads

  • Now that we have our Producer and Consumer classes ready, we can go ahead and start these threads to see wait and notify() in action in Java.
  • So we create two threads one producerThread and another consumerThread with name Producer and Consumer resp.
  • We will then start producerThread and consumerThread which will in turn call run method to start the execution.
  • We have kept the maxCount as 5 as of now.

Invocation of Producer and Consumer Threads

public static void main(String[] args) {
        List list = new ArrayList();
        int maxCount = 5;

        Thread producerThread = new Thread(new Producer(list, maxCount), "Producer");
        Thread consumerThread = new Thread(new Consumer(list), "Consumer");

        producerThread.start();
        consumerThread.start();
    }

5.4 Output

Here is the output of running above code. Since maxCount is very small, producer and consumer seems to work sequentially. You can increase the maxCount to, let’s say, 1000 and you will notice that consumer starts consuming before producer can insert all 1000 elements in sharedList

Output of the Example

Producer producing: 0
Producer producing: 1
Producer producing: 2
Producer producing: 3
Producer producing: 4
Consumer consumed: 0
Consumer consumed: 1
Consumer consumed: 2
Consumer consumed: 3
Consumer consumed: 4
Producer producing: 5
Producer producing: 6
Producer producing: 7
Producer producing: 8
Producer producing: 9
Consumer consumed: 5
Consumer consumed: 6
Consumer consumed: 7
Consumer consumed: 8
Consumer consumed: 9

6. Summary

So this example demonstrates how wait work in Java. Also, how wait and notify can be used to set up communication between multiple threads.

7. Download the Source Code

Download
You can download the full source code of this example here: Java Wait Example

Aashu Aggarwal

Aashu has graduated in Computer Science from Kurukshetra University. During her career she has been mostly involved with Java and related frameworks like Spring, Spark along with Python, NodeJs. She has been developing and architecting on projects for fin-tech companies, retail, telecommunications to name a few. Currently she is working as Chief Agile Officer as well as Delivery head for a startup in India where she is setting up company wide processes for projects and working on projects based on Java, Python, Big Data and Machine Learning.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button