Objects communicating across threads

Since version 1.5, the JDK has included the java.util.concurrent package. It was developed by experienced gurus, and includes a number of items for creating many kinds of multi-threaded programs. Java Concurrency in Practice by Goetz and others is a good reference for this package.

Here's a basic example of using ExecutorService and CountDownLatch from java.util.concurrent to communicate between threads. The example has the following context: in client side programming, it's common to show the user a list of their available printers. However, the JDK's tool for fetching this information can have poor performance - sometimes resulting in delays of several seconds or more. So, one option is to fetch the list of available printers early upon startup of the application, in a separate worker thread. Later, when the user wishes to print something, the list of available printers will already be pre-fetched.

import java.util.List;
import javax.print.PrintService;

/** 
 Toy client application. 
 Needs to present a list of printers to the end user.
 The problem is that fetching the list of printers can take several seconds.
 So, the idea is to start fetching the list in a worker thread upon startup.
*/ 
public final class LaunchApplication {

  public static void main(String... args){
    log("Launching application.");
    PrinterListDAO.init(); //launches worker thread
    
    //construct the user interface, other start tasks, etc...
    
    //use the list of printers later in processing
    List<PrintService> printers = new PrinterListDAO().getPrinters();
    log("Seeing this many printers:" + printers.size());
  }
  
  private static void log(String msg){
    System.out.println(msg);
  }
} 

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.print.PrintService;

final class PrinterListDAO {

  /** This must be called early upon startup. */
  static void init(){
    latch = new CountDownLatch(1);
    worker = new PrinterListWorker(latch);
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.execute(worker);
    executor.shutdown(); //reclaims resources
  }

  /** Return the list of printers that can print PDFs (double-sided, portrait).*/
  List<PrintService> getPrinters(){
    try {
      //block until the worker has set the latch to 0:
      latch.await();
    }
    catch (InterruptedException ex){
      log(ex.toString());
      Thread.currentThread().interrupt();
    }
    return worker.getPrinterList();
  }
  
  // PRIVATE
  
  /** Used to communicate between threads. */
  static private CountDownLatch latch;
  
  static private PrinterListWorker worker;
  
  private static void log(String msg){
    System.out.println(msg);
  }
} 

import java.util.*;
import java.util.concurrent.CountDownLatch;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.attribute.standard.Sides;
import javax.print.DocFlavor;

/** Look up the list of printers. */
final class PrinterListWorker implements Runnable {

  /** When the work is done, the latch will count down to 0. */
  PrinterListWorker(CountDownLatch latch){
    this.latch = latch;  
  }
  
  @Override public void run() {
    log("Worker thread started...");
    long start = System.nanoTime();

    //double-sided, portrait, for PDF files.
    PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
    attrs.add(Sides.DUPLEX);
    attrs.add(OrientationRequested.PORTRAIT);
    //this can take several seconds in some environments:
    printServices = Arrays.asList(
      PrintServiceLookup.lookupPrintServices(DocFlavor.INPUT_STREAM.PDF, attrs)
    );
    
    long end = System.nanoTime();
    log("Finished fetching list of printers. Nanos: " + (end-start));
    log("Num printers found:" + printServices.size());
    latch.countDown();
  }

  /** Return an unmodifiable list of printers. */
  List<PrintService> getPrinterList(){
    return Collections.unmodifiableList(printServices);
  }
  
  // PRIVATE
  
  /** Used to communicate between threads. */
  private CountDownLatch latch;

  private List<PrintService> printServices;
  
  private static void log(String msg){
    System.out.println(msg);
  }
} 
Here's an example run:

Launching application.
Worker thread started...
Finished fetching list of printers. Nanos: 586542213
Num printers found:0
Seeing this many printers:0

Older JDKs

If a modern JDK is not available to you, then you can't use the services of java.util.concurrent. Instead, you will need to rely on other means - often the Thread class, and the wait and notify methods of the Object class.

The following technique uses the wait-loop idiom and notifyAll. To be safe, always use notifyAll instead of notify. (As an optimization, notify can be used instead of notifyAll, but only if you know exactly what you are doing.)

Important points:

Example

In this example, the Airplane always needs to check with the Airport to see if it has an available runway before it's able to take off or land.

/** Uses wait loop idiom for inter-thread communication. */
public final class Airplane implements Runnable {

  public Airplane (Airport airport, String flightId){
    this.airport = airport;
    this.flightId = flightId;
  }

  @Override public void run() {
    takeOff();
    fly();
    land();
  }

  // PRIVATE 
  private Airport airport;
  private String flightId;

  private void takeOff() {
    synchronized(airport) {
      //always use a while loop, never an if-statement:
      while (!airport.hasAvailableRunway()) {
        log(flightId + ": waiting for runway...");
        try {
          //wait for notification from the airport that
          //the state of the airport has changed.
          //wait must always occur within a synchronized block
          airport.wait();
        }
        catch (InterruptedException ex) {
          Thread.currentThread().interrupt();
          System.err.println(ex);
        }
      }
      //there is an available runway now, so we may take off
      log(flightId + ": taking off now...");
    }
  }

  private void fly() {
    log(flightId + ": flying now...");
    try {
      //do nothing for several seconds
      Thread.sleep(10000);
    }
    catch (InterruptedException ex){
      log(ex.getMessage());
      Thread.currentThread().interrupt();
    }
  }

  private void land() {
    synchronized(airport) {
      while (!airport.hasAvailableRunway()) {
        //wait for notification from the airport that
        //the state of the airport has changed.
        log(flightId + ": waiting for runway...");
        try {
          airport.wait();
        }
        catch (InterruptedException ex) {
          System.err.println( ex );
          Thread.currentThread().interrupt();
        }
      }
      //there is an available runway now, so we may take off
      log(flightId + ": landing now...");
    }
  }
  
  private void log(String msg) {
    System.out.println(msg);
  }
} 

/** Notifies Airplanes when its state changes. */
public final class Airport implements Runnable {

  public Airport(String name) {
    super();
    this.name = name;
  }

  public synchronized boolean hasAvailableRunway() {
    return hasAvailableRunway;
  }

  @Override public void run() {
    System.out.println("Running " + name + " Airport.");
    while (true) {
      try {
        synchronized(this) {
          //simply toggle the state between available and not available
          hasAvailableRunway = !hasAvailableRunway;
          System.out.println(name + " Has Available Runway: " + hasAvailableRunway);
          //notify all waiters of the change of state
          notifyAll();
        }
        //pause execution for a few seconds
        Thread.sleep(1000);
      }
      catch (InterruptedException ex){
        System.err.println(ex);
        Thread.currentThread().interrupt();
      }
    }
  }

  //PRIVATE
  private boolean hasAvailableRunway = true;

  private String name;
} 

An example run of the following FlightSimulator class resulted in this output:

Running Flight Simulator.
Terminating the original user thread.
Running Charles de Gaulle Airport.
Charles de Gaulle Has Available Runway: false
Flight 8875: waiting for runway...
Charles de Gaulle Has Available Runway: true
Flight 8875: taking off now...
Flight 8875: flying now...
Charles de Gaulle Has Available Runway: false
Charles de Gaulle Has Available Runway: true
Charles de Gaulle Has Available Runway: false
Charles de Gaulle Has Available Runway: true
Charles de Gaulle Has Available Runway: false
Charles de Gaulle Has Available Runway: true
Charles de Gaulle Has Available Runway: false
Charles de Gaulle Has Available Runway: true
Charles de Gaulle Has Available Runway: false
Flight 8875: waiting for runway...
Charles de Gaulle Has Available Runway: true
Flight 8875: landing now...
Charles de Gaulle Has Available Runway: false
Charles de Gaulle Has Available Runway: true

/** 
 Builds and starts threads for Airport and Airplanes.  
 (Using raw Thread objects is discouraged in favour of the
 more modern java.util.concurrent package.)
*/
public final class FlightSimulator {

  public static void main(String... arguments) {
    System.out.println("Running Flight Simulator.");

    //build an airport and start it running
    Airport charlesDeGaulle = new Airport("Charles de Gaulle");
    Thread airport = new Thread(charlesDeGaulle);
    airport.start();

    //build a plane and start it running
    Thread planeOne = new Thread(new Airplane(charlesDeGaulle, "Flight 8875"));
    planeOne.start();

    //notice that this user thread now ends, but the program itself does
    //NOT end since the threads created above are also user
    //threads. All user threads have equal status, and there
    //is nothing special about the thread which launches a program.
    System.out.println("Terminating the original user thread.");
  }
} 

See Also :
Launch thread is just another user thread