Monitors, Synchronization & Deadlocks

Why do we need Synchronization?

Let’s look at an example of a Java program where we ignore the problem, and see what trouble we get into.

Example: A Steam Boiler

The following program is a simple emulation of a steam boiler. The boiler has a number of burners (this is a big boiler!), each one of which is programmed to burn fuel to keep the steam pressure up, unless the pressure exceeds a certain safety limit.

Class SteamBoiler represents the boiler, and class Burner represents a single burner. Each burner runs as a separate thread, and calls its burn() method to “burn fuel”. The burn() method has a check to make sure that the pressure does not go beyond the safety limit.

You’ll note that each burner just kicks in once, and then stops — i.e. its thread dies. This is not realistic, but is done to keep things relatively simple. When all the threads have died, the main program prints out the resulting pressure, and exits.

Here’s the code:

package unsynchronized;

/**
 *  NOTE: This implementation of the SteamBoiler class is implemented
 *  without regard to synchronization.  Because of this, it fails
 *  to maintain boiler pressure below the safety limit.
 */
public class SteamBoiler
{
  public static final int PRESSURE_LIMIT = 20;
  public static final int BURNER_COUNT = 10;
  
  public Burner[] start()
  {
    Burner[] burners = new Burner[BURNER_COUNT];
    for (int burner = 0; burner < BURNER_COUNT; burner++)
    {
      burners[burner] = new Burner(this);
      burners[burner].start();
    }
    
    return burners;
  }
  
  public int getPressure()
  {
    return m_pressure;
  }
  
  public void setPressure(int pressure)
  {
    m_pressure = pressure;
  }
  
  public static void main(String[] args)
  {
    SteamBoiler boiler = new SteamBoiler();
    
    Burner[] burners = boiler.start();
    
    for (int burner = 0; burner < BURNER_COUNT; burner++)
    {
      try
      {
        burners[burner].join();     // Wait for thread to finish
      }
      catch (InterruptedException e)
      {
        // This thread was interrupted.
      }
    }
    
    System.out.println("Pressure reads " + boiler.getPressure() +
        ", limit is " + PRESSURE_LIMIT);
  }
  
  /////// Data //////
  private int m_pressure = 0;
}

class Burner extends Thread
{
  public static final int PRESSURE_INCREMENT = 15;
  
  public Burner(SteamBoiler boiler)
  {
    m_boiler = boiler;
  }
  
  public void run()
  {
    burn();
  }
  
  private void burn()
  {
    if (m_boiler.getPressure() <
        SteamBoiler.PRESSURE_LIMIT - PRESSURE_INCREMENT)
    {
      // Wait to simulate delay
      try
      {
        sleep(100);
      }
      catch (InterruptedException e)
      {
        // Ignore
      }
      
      m_boiler.setPressure( m_boiler.getPressure() +
          PRESSURE_INCREMENT );
    }
  }
  
  ///// Private data //////
  private SteamBoiler m_boiler;
}

However, when the program is run, you’ll find that it outputs the following:

Pressure reads 150, limit is 20

(The first number will vary because of thread timing variations.)

Here’s a program that uses the above class and displays the boiler pressure in a more graphical form:

package unsynchronized;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import unsynchronized.SteamBoiler;

/**
 *  This class provides a visual representation of the SteamBoiler.
 */
public class SteamBoilerFrame extends JFrame implements Runnable
{
  /**
   * Main entry point
   */
  public static void main(String[] args)
  {
    SteamBoilerFrame frame = new SteamBoilerFrame();
    frame.setDefaultLookAndFeelDecorated(true);
    frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
    frame.setSize(300, 200);
    frame.setVisible(true);
  }
  
  public SteamBoilerFrame()
  {
    setTitle("Steam Boiler");
    m_gauge = new PressureGauge( new SteamBoiler() );
    setContentPane( createBoilerLayout() );
    // Start this thread
    m_timer = new Thread(this);
    m_timer.start();    // Start the timer
  }
  
  public void stop()
  {
    m_timer = null; // Stop the timer
  }
  
  private JPanel createBoilerLayout()
  {
    JPanel panel = new JPanel( new BorderLayout() );
    panel.setBackground(Color.lightGray);
    
    JLabel label = new JLabel("Steam Boiler (Max Pressure = " +
                              SteamBoiler.PRESSURE_LIMIT + ")"
                              );
    JPanel north = new JPanel( new FlowLayout(FlowLayout.CENTER) );
    north.add(label);
    panel.add(north, BorderLayout.NORTH);
    
    panel.add(m_gauge, BorderLayout.CENTER);
    
    JPanel south = new JPanel( new FlowLayout(FlowLayout.CENTER) );
    m_startButton.addActionListener( new ActionListener()
      {
        public void actionPerformed(ActionEvent event)
        {
          m_startButton.setEnabled(false);
          m_gauge.getBoiler().start();   // Start the boiler
        }
      }
    );
    south.add(m_startButton);
    
    m_lastPressure = m_gauge.getPressure();
    m_pressureText = new JLabel(PRESSURE_TEXT + m_lastPressure);
    south.add(m_pressureText);
    
    m_resetButton.addActionListener( new ActionListener()
      {
        public void actionPerformed(ActionEvent event)
        {
          m_gauge.setBoiler( new SteamBoiler() );
          m_startButton.setEnabled(true);
        }
      }
    );
    south.add(m_resetButton);
    panel.add(south, BorderLayout.SOUTH);
    
    return panel;
  }
  
    /*
     *   This constitutes a timer thread to refresh the gauge
     *   on a regular basis.
     */
  public void run()
  {
    while (m_timer != null)
    {
      try
      {
        Thread.sleep(100);
      }
      catch (InterruptedException e)
      {
        // Ignore
      }
      
      // Repaint if the pressure has changed since last time
      int pressure = m_gauge.getPressure();
      if (pressure != m_lastPressure)
      {
        m_lastPressure = pressure;
        m_pressureText.setText(PRESSURE_TEXT + pressure);
        m_gauge.repaint();
      }
    }
  }
  
  ///////// Private Data //////
  private boolean         m_laidOut = false;
  private Thread          m_timer;
  private PressureGauge   m_gauge;
  private JButton         m_startButton = new JButton("Burn!");
  private JButton         m_resetButton = new JButton("Reset");
  private JLabel          m_pressureText;
  private int             m_lastPressure = 0;
  
  private static final String PRESSURE_TEXT = "Pressure = ";
}

class PressureGauge extends JPanel
{
  public PressureGauge(SteamBoiler boiler)
  {
    setBoiler(boiler);
  }
  
  public void setBoiler(SteamBoiler boiler)
  {
    m_boiler = boiler;
    repaint();
  }
  
  public SteamBoiler getBoiler()
  {
    return m_boiler;
  }
  
  public int getPressure()
  {
    return m_boiler.getPressure();
  }
  
  public void paintComponent(Graphics g)
  {
    super.paintComponent(g);
    Dimension dim = getSize();
    int x = (dim.width - OUTER_WIDTH)/2;
    int y = 0;
    
    // Draw outer white area
    g.setColor(Color.WHITE);
    g.fillRect(x, y, OUTER_WIDTH, dim.height);
    
    // Draw outline
    g.setColor(Color.black);
    int width = OUTER_WIDTH - INSET - INSET;
    int height = dim.height - INSET - INSET;
    g.drawRect(x + INSET - 1, y + INSET - 1, width + 1, height + 1);
    
    // Draw the "mercury"
    Color color = Color.blue;
    if (getPressure() > SteamBoiler.PRESSURE_LIMIT)
      color = Color.red;
    g.setColor(color);
    
    float factor = (float) getPressure() / (float) (SteamBoiler.PRESSURE_LIMIT * 10);
    height = (int) ((float)height * factor);
    g.fillRect(x + INSET, dim.height - height - INSET, width, height);
  }
  
  ///// Private data ///////
  private static final int OUTER_WIDTH = 20;
  private static final int INSET = 5;
  
  private SteamBoiler m_boiler;
}

and here’s what it produces (a video):

Perhaps I should add some explosive effects?

So what’s going on?

Race Conditions

The problem is a classical problem in multi-threaded code — a race condition. What you probably expected to happen was:

  1. Thread A reads the pressure gauge
  2. Thread A updates the pressure gauge
  3. Thread B reads the pressure gauge
  4. Thread B updated the pressure gauge

However, what actually happens is more like:

  1. Thread A reads the pressure gauge
  2. Thread B reads the pressure gauge
  3. Thread A updates the pressure gauge
  4. Thread B updates the pressure gauge

In other words, Thread A was preempted by Thread B, and then back again, and then again. The result is that Thread A’s update is overwritten by Thread B, and the check that you thought was happening is made useless.

When you have multiple concurrent activities updating a shared data item, you have to be more careful — you have to synchronize access to that data item.

Monitors

The concept of a monitor was pioneered in the 1970s by C.A.R. Hoare and Per Brinch Hansen

A monitor applies the principle of mutual exclusion to a set of procedures. When mutual exclusion occurs, only a single thread can have access to a shared resource at a time. Other threads that attempt to access the shared resource are forced to wait until the thread that is accessing the resource has done with it..

In order for a thread to gain the necessary access to a shared resource, it must acquire a lock and hold it until the thread has completed the necessary actions on that resource. Then it must release the lock, so that any waiting threads may compete to acquire the lock. Only one of the threads will win, and the rest continue to wait.

This mechanism assures that access to the shared resource is serialized, so that only one thread may access the resource at a time.

In general, it is good practice to minimize the amount of time that a thread can hold a lock. If a thread holds a lock too long, it can negatively impact the performance of the system.

Note: Per Brinch Hansen, one of the pioneers of monitors was not pleased at what he saw in how Java implemented the idea of a monitor.  See http://brinch-hansen.net/papers/1999b.pdf for more details.

Synchronization

In Java, every object has an associated monitor which may be used to serialize access to that object. Also, every class has an associated monitor to serialize access to the class. The Java Virtual Machine implements the necessary support for these monitors.

The Java language adds a construct to allow a programmer to specify when a monitor is to be used — the synchronized keyword:

The synchronized statement performs two special actions relevant only to multithreaded operation:

  1. After computing a reference to an object but before executing its body, it locks a lock associated with the object.
  2. After execution of the body has completed, either normally or abruptly, it unlocks that same lock. As a convenience, a method may be declared synchronized; such a method behaves as if its body were contained in a synchronized statement.

(The Java Virtual Machine Specification, by Tim Lindholm & Frank Yellin, published by Addison Wesley, 1997, page 53.)

Here are some examples of the use of synchronized keyword:

package threads;

public class SharedResource
{
    // ...

    private String m_value = "";

    public synchronized void setValue(String value)  // 1
    {
        m_value = value;
    }

    public synchronized String getValue()	     // 2
    {
        return m_value;
    }

    private static int m_count = 0;

    public static synchronized void incrementCount()  // 3
    {
        m_count++;
    }

    private Double m_salary = new Double(32000.0);

    public void giveRaise(double percent)
    {
        synchronized (m_salary) 		       // 4
        {
            double salary = m_salary.doubleValue();
            salary += salary * percent /100;
            m_salary = new Double(salary);
        }
    }
}

Methods 1 and 2, because they are instance methods, synchronize on the object’s (or instance’s) monitor.

Method 3, because it is a class (static) method, synchronizes on the class’ monitor.

The synchronized statement at 4 specifies that it synchronizes on m_salaryan instance of class Double.

Note: The synchronization may not be on an instance of a primitive type, such as double.

Note that methods getValue()incrementCount() and raiseSalary() may all be executing concurrently in different threads, because they synchronize on different monitors.

A thread may call a synchronized method on an object for which it already holds the lock. This is called reacquiring the lock, and is allowed because Java monitors are reentrant. Because of this, a single thread cannot deadlock itself on a lock it already holds.

Example: A Fixed Steam Boiler

Now, let’s go back to our SteamBoiler class, and change the burn() method in class Burner to:

private void burn()
{
    synchronized (m_boiler)
    {
        if (m_boiler.getPressure() < 
                   SteamBoiler.PRESSURE_LIMIT - PRESSURE_INCREMENT)
        {
            // Wait to simulate delay
            try
            {
                sleep(100);
            }
            catch (InterruptedException e)
            {
                // Ignore
            }
                
            m_boiler.setPressure( m_boiler.getPressure() + 
                                  PRESSURE_INCREMENT );
        }
    }
}

The only change is to surround the code with a synchronized statement, specifying the instance of SteamBoiler as the object on which to synchronize. 

Why did I choose to synchronize on the instance of SteamBoiler? Why did I not simply make the burn() method synchronized?

To show it works, here’s the fixed SteamBoiler class:

package threads;

/**
 *  NOTE: This implementation of the SteamBoiler class is implemented
 *  with regard to synchronization.  
 */
public class SteamBoiler
{
  public static final int PRESSURE_LIMIT = 20;
  public static final int BURNER_COUNT = 10;
  
  public Burner[] start()
  {
    Burner[] burners = new Burner[BURNER_COUNT];
    for (int burner = 0; burner < BURNER_COUNT; burner++)
    {
      burners[burner] = new Burner(this);
      burners[burner].start();
    }
    
    return burners;
  }
  
  public int getPressure()
  {
    return m_pressure;
  }
  
  public void setPressure(int pressure)
  {
    m_pressure = pressure;
  }
  
  public static void main(String[] args)
  {
    SteamBoiler boiler = new SteamBoiler();
    
    Burner[] burners = boiler.start();
    
    for (int burner = 0; burner < BURNER_COUNT; burner++)
    {
      try
      {
        burners[burner].join();     // Wait for thread to finish
      }
      catch (InterruptedException e)
      {
        // This thread was interrupted.
      }
    }
    
    System.out.println("Pressure reads " + boiler.getPressure() +
      ", limit is " + PRESSURE_LIMIT);
  }
  
  /////// Data //////
  private int m_pressure = 0;
}

class Burner extends Thread
{
  public static final int PRESSURE_INCREMENT = 15;
  
  public Burner(SteamBoiler boiler)
  {
    m_boiler = boiler;
  }
  
  public void run()
  {
    burn();
  }
  
  private void burn()
  {
    synchronized (m_boiler)
    {
      if (m_boiler.getPressure() <
        SteamBoiler.PRESSURE_LIMIT - PRESSURE_INCREMENT)
      {
        // Wait to simulate delay
        try
        {
          sleep(100);
        }
        catch (InterruptedException e)
        {
          // Ignore
        }
        
        m_boiler.setPressure( m_boiler.getPressure() +
          PRESSURE_INCREMENT );
      }
    }
  }
  
  ///// Private data //////
  private SteamBoiler m_boiler;
}

When this program is run, it outputs:

Pressure reads 15, limit is 20

The graphical test program remains unchanged, and now produces (a video):

Much safer!

Deadlock

We said that, for a given thread that has already acquired the lock on a monitor, monitors are reentrant. However, if two (or more) threads compete for the same two (or more) resources, there is the possibility of deadlock, unless measures are taken to prevent it.

For example, imagine that we’re writing a program to transfer funds from one bank account to another, and we want to ensure that we have no problems when we are accessing accounts from different threads.

Here’s a possible program to do that:

package threads;

/**
*  This class will create a deadlock situation.
*/
public class Deadlock
{
    public static void main(String[] args)
    {
        Account a1 = new Account("Account 1", 10000);
        Account a2 = new Account("Account 2", 30000);
        Thread threadA = 
            new TransferFundsThread("Thread A", a1, a2, 5);
        Thread threadB = 
            new TransferFundsThread("Thread B", a2, a1, 20);
        threadA.start();
        threadB.start();
    }
}

class Account
{
    public Account(String name, int balance)
    {
        m_name = name;
        m_balance = balance;
    }
    
    public int getBalance()
    {
        return m_balance;
    }
    
    public void setBalance(int newBalance)
    {
        m_balance = newBalance;
    }
    
    public String getName()
    {
        return m_name;
    }
    
    private String m_name;
    private int    m_balance;
}

class TransferFundsThread extends Thread
{
    public TransferFundsThread(String name, 
                               Account from, 
                               Account to, 
                               int amount)
    {
        super(name);
        m_from   = from;
        m_to     = to;
        m_amount = amount;
    }
    
    public void run()
    {
        transferFunds();
    }
    
    /**
     *  In order to transfer funds safely, we need to be able 
     *  to hold a lock on both the m_from and the m_to accounts, 
     *  and not release the locks until the transfer is completed.
     *  NOTE: This version does not consider the possibility that 
     *  there may be insufficient funds available.  This has been 
     *  omitted to simplify the example, but would have to be done 
     *  in the real world.
     */
    private void transferFunds()
    {
        synchronized (m_from)
        {
            System.out.println(getName() + 
                               ": Getting balance from " + 
                               m_from.getName());
            int balance1 = m_from.getBalance();
            System.out.println(getName() + 
                               ": Balance from " + 
                               m_from.getName() + 
                               " = " + balance1);
                    
            try
            {
                sleep(100);         // insert delay
            }
            catch (InterruptedException e)
            {
                // ignore
            }
            
            synchronized (m_to)
            {
                System.out.println(getName() + 
                                   ": Getting balance from " + 
                                   m_to.getName());
                int balance2 = m_to.getBalance();
                System.out.println(getName() + 
                                   ": Balance from " + 
                                   m_to.getName() + 
                                   " = " + balance2);
                                   
                if (balance1 >= m_amount)
                {
                    System.out.println(getName() + 
                                       ": Withdrawing funds from " +
                                       m_from.getName());
                    m_from.setBalance(balance1 - m_amount);
                    System.out.println(getName() + 
                                       ": Depositing funds into " +
                                       m_to.getName());
                    m_to.setBalance(balance2 + m_amount);
                    
                    System.out.println(getName() + 
                                       ": New balances are:\n" +
                                       "  " + m_from.getName() + 
                                       " = " + 
                                       m_from.getBalance() + "\n" +
                                       "  " + m_to.getName() + " = " +
                                       m_to.getBalance() );
                }
            }
        }
    }
    
    /// Private data ///
    private Account m_from;
    private Account m_to;
    private int     m_amount;
}

But when you run the program, it outputs the following:

Thread A: Getting balance from Account 1
Thread B: Getting balance from Account 2
Thread A: Balance from Account 1 = 10000
Thread B: Balance from Account 2 = 30000

and then it stops. What’s happening? Well, if you’re running the program from a regular console window (for example, an MS-DOS window on Microsoft Windows), using the java program (not JRE), you can force a stack dump if you set the keyboard focus to the console window and press the proper keystroke combination:

  • On Microsoft Windows, press <CTRL>Break (that is, hold the CTRL key down and press Break)
  • On UNIX, press <CTRL>\ (that is, hold the CTRL key down and press the backslash key)

and you will get output that looks something like this (this one was generated on Microsoft Windows):

Full thread dump:
    "Thread B" (TID:0x133d0f0, sys_thread_t:0xd26150, Win32ID:0x14b, state:MW) prio=5
        threads.TransferFundsThread.transferFunds(Deadlock.java:70)
        threads.TransferFundsThread.run(Deadlock.java:55)
    "Thread A" (TID:0x133d110, sys_thread_t:0xd26210, Win32ID:0x15e, state:MW) prio=5
        threads.TransferFundsThread.transferFunds(Deadlock.java:70)
        threads.TransferFundsThread.run(Deadlock.java:55)
    "SymcJIT-LazyCompilation-0" (TID:0x133cf58, sys_thread_t:0xd269c0, Win32ID:0x141, state:CW) prio=2
        SymantecJITCompilationThread.run(JITcompilationthread.java, Compiled Code)
    "SymcJIT-LazyCompilation-PA" (TID:0x133cf20, sys_thread_t:0xd251f0, Win32ID:0x158, state:CW) prio=10
        java.lang.Object.wait(Object.java:307)
        SymantecJITCompilationThread.run(JITcompilationthread.java, Compiled Code)
    "Finalizer thread" (TID:0x1339088, sys_thread_t:0xd1c670, Win32ID:0x145, state:CW) prio=2
    "main" (TID:0x13390b0, sys_thread_t:0xd1b8b0, Win32ID:0x14f, state:CW) prio=5
Monitor Cache Dump:
    threads.Account@133D130/1429100: owner (0x7ffd9000, 1 entry)
    threads.Account@133D148/14290B8: owner (0x7ffda000, 1 entry)
Registered Monitor Dump:
    SymcJIT Method Monitor: <unowned>
    SymcJIT Method List Monitor: <unowned>
    SymcJIT Lock: <unowned>
    Thread queue lock: <unowned>
        Waiters: 1
    Name and type hash table lock: <unowned>
    String intern lock: <unowned>
    JNI pinning lock: <unowned>
    JNI global reference lock: <unowned>
    BinClass lock: <unowned>
    Class loading lock: <unowned>
    Java stack lock: <unowned>
    Code rewrite lock: <unowned>
    Heap lock: <unowned>
    Has finalization queue lock: <unowned>
    Finalize me queue lock: <unowned>
        Waiters: 1
    Monitor registry: owner (0x7ffd8000, 1 entry)

Here’s a quick synopsis of a Java Stack Trace:

The possible thread states are:

  • R — Running or runnable thread
  • S — Suspended thread
  • CW — Thread waiting on a condition variable
  • MW — Thread waiting on a monitor lock
  • MS — Thread suspended waiting on a monitor lock

and you’ll note that Thread A and Thread B are both in state: MW — monitor wait.

The thread stack trace shows where the threads are — both threads are at the same point in their respective code — both are attempting to acquire a lock on their respective m_to objects.

Can you explain what’s happening? Where is the problem? How can we solve it?

As you can see, when you’re dealing with multiple threads, you have to be aware of the possibility of deadlock, and try to avoid that.

Starvation & Livelock

There are also potentially problematic conditions that you need to be aware of when designing or implementing multi-threaded applications.  These conditions are known as starvation and livelock:

Starvation

Starvation describes a situation where a thread is unable to gain regular access to shared resources and is unable to make progress. This happens when shared resources are made unavailable for long periods by “greedy” threads. 

For example, suppose an object provides a synchronized method that often takes a long time to return. If one thread invokes this method frequently, other threads that also need frequent synchronized access to the same object will often be blocked.

Livelock

A thread often acts in response to the action of another thread. If the other thread’s action is also a response to the action of another thread, then livelock may result. As with deadlock, livelocked threads are unable to make further progress. However, the threads are not blocked — they are simply too busy responding to each other to resume work. 

This is analogous to two people attempting to pass each other in a corridor: Larry moves to his left to let Curly pass, while Curly moves to his right to let Larry pass. Seeing that they are still blocking each other, Larry moves to his right, while Curly moves to his left. They’re still blocking each other, so…

Thread Coordination

Why Do We Need Thread Coordination?

Threads are often interdependent. That is, one thread may depend on another thread (or several other threads) to perform an operation or service a request.

A common problem is when a user requests a program to do something (such as a time-consuming calculation, or a complex database query, or some other process that takes time). Typically, the user interacts with the graphical user interface provided by the Java program. If the requested operation were to be performed in the same thread as the user interface, the user interface would become non-responsive. (I’m sure you’ve encountered programs that have done this to you!).

In Java, it is easy to create a thread and do the time-consuming operation in that thread, thereby freeing up the user interface thread to remain responsive.

But this introduces another problem: Once the long-running operation has completed, how does it notify the user interface thread that the work is done, so that the appropriate updates may be made to the user interface?

This is just one example of how two threads have a need to coordinate with each other.

Producer/Consumer Relationships

producer/consumer relationship is a very common relationship among threads. In this kind of a relationship, the Producer thread is responsible for producing something (in this case, work), and the Consumer thread is responsible for consuming it (in this case performing the work). Note:

  • The Producer produces something and notifies the Consumer that it is available.
  • The Consumer can’t do anything until it’s given something to consume, so it must wait until there is something available.

The typical pseudo-code for a Producer is:

enter synchronized code
  produce data
  notify consumer
leave synchronized code

while the typical pseudo-code for a Consumer is:

enter synchronized code
  while there is no data available
    wait
  consume data
leave synchronized code

In theory, the Producer could produce things as fast as it can, regardless of whether the Consumer can keep up. In the real world, to avoid running out of resources, the Producer shouldn’t produce anything at a rate the the Consumer can’t match. For example, it is common for data to be passed from a Producer to a Consumer by means of a shared buffer. If the buffer fills up, then the Producer shouldn’t attempt to continue using that buffer, but should wait for room to become available in the buffer.

Conditions

A Consumer thread is typically waiting on some condition to become true. While the condition remains false, the Consumer thread must continue to wait.

The waiting is performed by calling the wait() method, and the notification is performed by calling the notify() method.

Every class has access to these methods, since they are implemented in the Object class — the ultimate global superclass.

Each of these methods must be called from within a synchronized block.

The wait() Method
public final void wait() throws InterruptedException

Waits to be notified by another thread of a change in this object. The current thread must own this object’s monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object’s monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution. This method should only be called by a thread that is the owner of this object’s monitor.

The wait() method:

  • Throws IllegalMonitorStateException if the current thread is not the owner of the object’s monitor.
  • Throws InterruptedException if another thread has interrupted this thread.
The notify() Method
public final void notify()

Wakes up a single thread that is waiting on this object’s monitor. A thread waits on an object’s monitor by calling one of the wait methods. This method should only be called by a thread that is the owner of this object’s monitor. A thread becomes the owner of the object’s monitor in one of three ways:

  • By executing a synchronized instance method of that object.
  • By executing the body of a synchronized statement that synchronizes on the object.
  • For objects of type Class, by executing a synchronized static method of that class.

Only one thread at a time can own an object’s monitor.

The notify() method:

  • Throws IllegalMonitorStateException if the current thread is not the owner of this object’s monitor.
Other wait() Methods

There are also two forms of the wait() method that may be used if the thread is not willing to wait forever:

public final void wait(long timeout) 
    throws InterruptedException

Waits to be notified by another thread of a change in this object. The thread releases ownership of this monitor and waits until either of the following two conditions has occurred:

  • Another thread notifies threads waiting on this object’s monitor to wake up either through a call to the notify method or the notifyAll method.
  • The timeout period, specified by the timeout argument in milliseconds, has elapsed. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.

This method should only be called by a thread that is the owner of this object’s monitor.

Parameters:

timeout – the maximum time to wait in milliseconds.

This wait() method:

  • Throws IllegalArgumentException if the value of timeout is negative.
  • Throws IllegalMonitorStateException if the current thread is not the owner of the object’s monitor.
  • Throws InterruptedException if another thread has interrupted this thread.
public final void wait(long timeout, int nanos) 
    throws InterruptedException

Waits to be notified by another thread of a change in this object. This method is similar to the wait method of one argument, but it allows finer control over the amount of time to wait for a notification before giving up. The thread releases ownership of this monitor and waits until either of the following two conditions has occurred:

  • Another thread notifies threads waiting on this object’s monitor to wake up either through a call to the notify method or the notifyAll method.
  • The timeout period, specified by timeout milliseconds plus nanos nanoseconds arguments, has elapsed.

The thread then waits until it can re-obtain ownership of the monitor and resumes execution This method should only be called by a thread that is the owner of this object’s monitor.

Parameters:

timeout – the maximum time to wait in milliseconds.

nanos – additional time, in nanoseconds range 0-999999.

This wait method:

  • Throws IllegalArgumentException if the value of timeout is negative or the value of nanos is not in the range 0-999999.
  • Throws IllegalMonitorStateException if the current thread is not the owner of this object’s monitor.
  • Throws InterruptedException if another thread has interrupted this thread.

Throws: 

IllegalMonitorStateException if the current thread is not the owner of this object’s monitor.

The notifyAll() Method

There are occasions when we’d like to have a number of threads waiting on a monitor. For example, there might be a number of threads capable of doing work as a Consumer. When there is work available, if the Producer calls notifyAll(), it causes all the threads waiting on that monitor to return from their wait():

public final void notifyAll()

Wakes up all threads that are waiting on this object’s monitor. A thread waits on an object’s monitor by calling one of the wait methods. This method should only be called by a thread that is the owner of this object’s monitor.

The notifyAll() method:

  • Throws IllegalMonitorStateException if the current thread is not the owner of this object’s monitor.

In general, notifyAll() tends to be safer than relying on notify().

In general, notifyAll() tends to be safer than relying on notify().

A Producer/Consumer Example

Here’s an example of a Producer and a Consumer class, with a SharedBuffer class that provides the necessary coordination between the two threads:

package threads;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


/**
 *  This is a Producer thread that supplies strings
 *  to a Consumer thread.  It reads a line from System.in
 *  and passes it to the Consumer through a SharedBuffer,
 *  until the end of stream occurs.
 */
public class Producer extends Thread
{
  public static void main(String[] args)
  {
    // Create a shared buffer
    SharedBuffer buffer = new SharedBuffer();
    // Create and start a consumer thread
    Consumer consumer = new Consumer(buffer);
    consumer.start();
    // Create and start a Producer thread
    Producer producer = new Producer(buffer);
    producer.start();
  }
  
  Producer(SharedBuffer buffer)
  {
    super("Producer");
    m_buffer = buffer;
  }
  
  public void run()
  {
    // Connect up to System.in
    BufferedReader reader = new BufferedReader(
      new InputStreamReader(System.in)
      );
    
    try
    {
      // Loop, asking for input from the user until EOF
      while (true)
      {
        String line = reader.readLine();
        if (line == null)
          break;      // End of input
        m_buffer.putData(line);
      }
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
    
    System.out.println("Done with input: Signalling to Consumer");
    m_buffer.putData(null);
  }
  
  /// Private data ///
  private SharedBuffer m_buffer;
}

/**
 *  This is a Consumer thread that waits for input from
 *  the Producer, supplied through a SharedBuffer.  It
 *  accepts the string passed, reverses it, and prints out
 *  the result.  When it encounters a null string, the thread
 *  exits.
 */
class Consumer extends Thread
{
  Consumer(SharedBuffer buffer)
  {
    super("Consumer");
    m_buffer = buffer;
  }
  
  public void run()
  {
    while (true)
    {
      String line = m_buffer.getData();
      if (line == null)
        break;
      // Reverse the line and print it out
      line = reverse(line);
      System.out.println(line);
    }
    
    System.out.println("Consumer thread exiting...");
  }
  
  private String reverse(String string)
  {
    StringBuffer buffer = new StringBuffer(string);
    return buffer.reverse().toString();
  }
  
  //// Private data ////
  private SharedBuffer m_buffer;
}

/**
 *  This is a SharedBuffer class to which the Producer puts
 *  data, and from which the Consumer retrieves it.
 *  It contains the necessary thread coordination code to
 *  ensure that the Producer and Consumer work together properly.
 */
class SharedBuffer
{
  static final int RESOURCE_LIMIT = 10;
  
  boolean isFull()
  {
    return m_data.size() > RESOURCE_LIMIT;
  }
  
  boolean isEmpty()
  {
    return m_data.size() <= 0;
  }
  
  synchronized void putData(String s)
  {
    while ( isFull() )  // Wait until there's room
    {
      try
      {
        wait();
      }
      catch (InterruptedException e)
      {
        // Ignore
      }
    }
    
    m_data.add(s);   // To the end of the FIFO list
    
    notify();       // Notify any waiter
  }
  
  synchronized String getData()
  {
    while ( isEmpty() ) // Wait until there's data available
    {
      try
      {
        wait();
      }
      catch (InterruptedException e)
      {
        // Ignore
      }
    }
    
    String data = (String)(m_data.get(0)); // Get first element
    if (data != null)
      m_data.remove(data);     // and remove it from the List
    
    notify();       // Notify any waiter
    
    return data;
  }
  
  //// Private data ////
  
  // A list of strings.
  private List<String> m_data = new ArrayList<String>();
  private boolean m_done = false;
}

Here’s a simple example of one run of this program:

Hello!
!olleH
How are you?
?uoy era woH
Nice talking to you!
!uoy ot gniklat eciN
Goodbye
eybdooG
Done with input: Signalling to Consumer
Consumer thread exiting...

Communication Between Threads

Using Pipes to Communicate between Threads

Sometimes the communication requirements between threads is quite simple. Take, for example, the case where the producer creates a stream of characters and the consumer reads and processes that stream.

Java has a convenient set of Piped classes that provide the support for just such a circumstance:

  • PipedReader and PipedWriter, for text data
  • PipedInputStream and PipedOutputStream, for non-text data

An Example

Let’s modify the previous Producer/Consumer example to use Piped classes for coordination:

package threads;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.PrintWriter;

/**
 *  This is a PipedProducer thread that supplies strings
 *  to a PipedConsumer thread.  It reads a line from System.in
 *  and passes it to the PipedConsumer through a SharedBuffer,
 *  until the end of stream occurs.
 */
public class PipedProducer extends Thread
{
  public static void main(String[] args)
  {
    try
    {
      // Create the input and output pipes and hook them up
      PipedWriter writer = new PipedWriter();
      PipedReader reader = new PipedReader(writer);
      // Create and start a consumer thread
      PipedConsumer consumer = new PipedConsumer(reader);
      consumer.start();
      // Create and start a PipedProducer thread
      PipedProducer producer = new PipedProducer(writer);
      producer.start();
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
  }
  
  PipedProducer(PipedWriter writer)
  {
    super("Producer");
    m_writer = new PrintWriter(writer);
  }
  
  public void run()
  {
    // Connect up to System.in
    BufferedReader reader = new BufferedReader(
      new InputStreamReader(System.in)
      );
    
    try
    {
      // Loop, asking for input from the user until EOF
      while (true)
      {
        String line = reader.readLine();
        if (line == null)
          break;      // End of input
        m_writer.println(line);
      }
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
    
    System.out.println("Done with input: Closing pipe");
    m_writer.close();
  }
  
  /// Private data ///
  private PrintWriter m_writer;
}

/**
 *  This is a PipedConsumer thread that waits for input from
 *  the PipedProducer, supplied through a SharedBuffer.  It
 *  accepts the string passed, reverses it, and prints out
 *  the result.  When it encounters a null string, the thread
 *  exits.
 */
class PipedConsumer extends Thread
{
  PipedConsumer(PipedReader reader)
  {
    super("Consumer");
    m_reader = new BufferedReader(reader);
  }
  
  public void run()
  {
    try
    {
      while (true)
      {
        String line = m_reader.readLine();
        if (line == null)
          break;
        // Reverse the line and print it out
        line = reverse(line);
        System.out.println(line);
      }
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
    
    System.out.println("Consumer thread exiting...");
  }
  
  private String reverse(String string)
  {
    StringBuffer buffer = new StringBuffer(string);
    return buffer.reverse().toString();
  }
  
  //// Private data ////
  private BufferedReader m_reader;
}

Here’s an example of the output from one run of this program:

Hello there!
!ereht olleH
Nice to see you!
!uoy ees ot eciN
Gotta go!
!og attoG
Done with input: Closing pipe
Consumer thread exiting...