Java programming course: 12.4 Thread waiting and notification

In the previous lesson you saw how to implement the Runnable interface.


Thread waiting and notification

For some types of multithreaded application, using the synchronized keyword alone is not enough on its own. Imagine that at the zoo the penguins are fed fish from a barrel in the following manner:

  • The barrel has a capacity of 15 fish in total. Fish have to be placed into the barrel one at a time, and each fish can only be taken from the barrel one at a time
  • A zookeeper has a total of 35 fish that can be put in the barrel
  • Each penguin can compete with other penguins for the fish, but each one can only eat a maximum of 10 fish

Create a FishBarrel class follows:

package virtualzoo.core;

public class FishBarrel {
    
    public static final int CAPACITY = 15;

    private int fishCount;

    public FishBarrel() {
        fishCount = 0;
    }

    public synchronized int getCount() {
        return fishCount;
    }

    public synchronized boolean isEmpty() {
        return fishCount == 0;
    }

    public synchronized boolean isFull() {
        return fishCount == CAPACITY;
    }

    public synchronized void addFish() {
        // To be completed...
    }

    public synchronized void takeFish() {
        // To be completed...
    }

}
 
  • The above class includes the instance variable fishCount to hold the number of fish currently inside the barrel. Methods are included to return that count, and return whether the barrel is empty or full
  • The addFish() method will be invoked by a zookeeper thread, and provided the barrel is not already full a new fish will be added to it
  • The takeFish() method will be invoked by a penguin thread, and provided the barrel is not empty a fish will be removed from it
  • All of the methods are sychronized because they all access the fishCount instance variable which is to be shared among multiple threads

You now need to provide the code for the addFish() and takeFish() methods. Looking first at addFish(), here is what it needs to do:

  1. Check whether the barrel is already full. If so, wait until it isn't
  2. Provided the barrel is not full then add one fish to it
  3. Notify other threads which might be waiting for the barrel to contain some fish that at least one fish now exists in the barrel. This might be the case if a particular penguin thread found an empty barrel, so this notification will allow that thread to try again

Here is the code to achieve the above steps:

public synchronized void addFish() {
    // Check whether barrel is already full
    while (isFull()) {
        // Barrel is full so wait
        try {
            wait();
        } catch (InterruptedException ex) {
            System.out.println(ex.getMessage());
        }
    }

    // If reached here barrel is not full so add a fish
    fishCount++;

    // Notify other waiting threads that state of barrel has changed
    notifyAll();
}
 
  • The wait() method is defined in Object and is therefore available to every class through inheritance. It causes the lock on the object to be released to give a chance for a different thread to run. In this case you are waiting for a different thread to take a fish from the barrel, so it is no longer full
  • Note the following about the wait() method:
  • It can throw an InterruptedException so this needs to be caught, although this won't occur in this simple application as no other thread will cause an interrupt
  • It is nested inside a while loop. It is very important that you use a while loop rather than just an if condition because when this thread gets control back it needs to recheck whether the condition (isFull() in this case) has actually changed
  • After the fish count has been incremented, a call is made to notifyAll(). This is another method inherited through Object, and it serves to notify all other waiting threads that are themselves waiting that they can try and continue.

There is also a notify() method which notifies a single thread, but it is up to the scheduler which one is notified (and is effectively random). If you have more than one waiting thread this means that the one that gets notified might not be the one that does what you are waiting for. Because of this drawback, unless you know for certain that there is only one other thread then it is strongly recommended that you use notifyAll() rather than notify().

The code for the takeFish() method follows a very similar pattern:

public synchronized void takeFish() {
    // Check whether barrel is currently empty
    while (isEmpty()) {
        // Wait until barrel no longer empty
        try {
            wait();
        } catch (InterruptedException ex) {
            System.out.println(ex.getMessage());
        }
    }

    // If reached here barrel is not empty so remove a fish
    fishCount--;

    // Notify other waiting threads that state of barrel has changed
    notifyAll();
}
 

Here is the code for a threaded zookeeper[1] class:

[1 ]For simplicity, this class does not extend ZooKeeper since it is not relevant to the example.
package virtualzoo.core;

public class FishKeeper implements Runnable {
    
    public static final int TOTAL_FISH = 35;

    private FishBarrel barrel;
    private int fishRemaining;

    public FishKeeper(FishBarrel barrel) {
        this.barrel = barrel;
        fishRemaining = TOTAL_FISH;
    }

    public int getFishRemaining() {
        return fishRemaining;
    }

    @Override
    public void run() {
        while (fishRemaining > 0) {
            barrel.addFish();
            fishRemaining--;
        }
    }

}
 

•The constructor takes a reference to a FishBarrel object, and the run() method adds the fish to the barrel one at a time

Here is the code for a threaded penguin:

package virtualzoo.core;

public class HungryPenguin implements Runnable {

    public static final int FISH_LIMIT = 10;

    private String name;
    private FishBarrel barrel;
    private int fishEaten;

    public HungryPenguin(String name, FishBarrel barrel) {
        this.name = name;
        this.barrel = barrel;
        fishEaten = 0;
    }

    public int getEatenCount() {
        return fishEaten;
    }

    @Override
    public void run() {
        while (fishEaten < FISH_LIMIT) {
            barrel.takeFish();
            fishEaten++;
            System.out.println(name + " has eaten fish " +fishEaten);
        }
    }

}
 
  • The constructor takes a reference to a FishBarrel object, and the run() method keeps taking a fish from the barrel until it has eaten its quota

In the Experiments class you can enter the following statements to simulate a barrel, a single threaded zookeeper and three threaded hungry penguins:

FishBarrel barrel = new FishBarrel();

FishKeeper keeper = new FishKeeper(barrel);

HungryPenguin penguin1 = new HungryPenguin("penguin1", barrel);
HungryPenguin penguin2 = new HungryPenguin("penguin2", barrel);
HungryPenguin penguin3 = new HungryPenguin("penguin3", barrel);

new Thread(keeper).start();
new Thread(penguin1).start();
new Thread(penguin2).start();
new Thread(penguin3).start();
        
// Allow time for all threads to complete
try {
    Thread.sleep(1000);
} catch (InterruptedException ex) {
    System.out.println(ex.getMessage());
}
System.out.println("--- TOTALS ---");
System.out.println("penguin1 eaten: " + penguin1.getEatenCount());
System.out.println("penguin2 eaten: " + penguin2.getEatenCount());
System.out.println("penguin3 eaten: " + penguin3.getEatenCount());
System.out.println("keeper has left: " + keeper.getFishRemaining());
System.out.println("fish left in barrel: " + barrel.getCount());
 

If you run the above you should see in the Output window that the order in which the penguins eat each fish is mixed, although they all end up eating their fill. Each time you run the code you could get a different order.

Writing multithreaded applications can get considerably more complex than the relatively simple examples in this section.


In the next series of lesson we will introduce you to graphical user interfaces.

Next lesson: 13.1 Introduction to graphical user interfaces 


Print
×
Stay Informed

When you subscribe, we will send you an e-mail whenever there are new updates on the site.

Related Posts

 

Comments

No comments made yet. Be the first to submit a comment
Saturday, 13 December 2025

Captcha Image