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
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:
wait
and notifyAll
must always occur within a synchronized
blockwait
must always be invoked within a while
loop, and not in an if
statementIn 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; }
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."); } }