Multithreaded execution (i.e., the running of multiple tasks simultaneously or concurrently) is an essential feature of the Java platform. The execution of each task is a thread.
Every application has at least one thread — or several, if you count "system" threads that do things like memory management and signal handling. But from the programmer's point of view, you start with just one thread, called the main thread. This thread has the ability to create additional threads.
On this page, we show several examples of multithreading adapted from the textbook. These examples are in chapter20.zip.
In a multithreading environment, threads need to be created, scheduled to run, paused, resumed, and terminated. Java makes the management of threads easy for programmers by providing high-level APIs for multithreaded programming.
The executor package in the chapter20 project shows how to start several threads. Each task that is to run as a separate thread must be specified in the run() method of the Runnable interface. In our example, the PrintTask and CountTask classes implement the Runnable interface. Once objects of a Runnable type are created, threads are started and scheduled by an object of the ExecutorService class. This is shown in the main() method of the TaskExecutor class.
It is important to observe that the order and the timing of operations performed by the threads are controlled by the runtime system and cannot be controlled by the programmer.
The hardest thing in multithreaded programming is to synchronize the operations performed by different threads. When multiple threads need to update information stored in a shared object, some ordering has to be enforced to avoid unintended consequences. Java provides a locking mechanism for this purpose. When one thread wants to access the shared object, it has to lock the object first, and when it finished using the object, it has to unlock the object. This way, each thread will have exclusive access to the object when the thread needs the object.
Instead of writing programs explicitly using a lock/unlock mechanism, Java provides a number of thread-safe classes that correctly and efficiently implement the locking. In the synch1 package, we show how the keyword synchronized in the add() method of the SimpleArray class (the object shared by two ArrayWriter threads) affects the order in which two threads access the array.
In the synch2 package, we consider a producer/consumer application, where one or more producer objects write data to a shared buffer and one or more consumer objects read (or consume) the data from the shared buffer. There are several ways to implement the shared buffer. Class UnsynchronizedBuffer shows that using an unsynchronized ArrayList as the buffer will not be thread-safe. The BlockedBuffer shows that the Java class ArrayBlockedQueue is inherently thread-safe. The classes CircularBuffer and SynchronizedBuffer show how to implement access methods to ensure thread synchronization.
In Java GUI programming, the view should be accessed from only one thread, a technique called thread confinement.
To prevent a long computation from making the GUI freeze, Java provides a SwingWorker class which can be extended to implement long computations in a separate thread.
In package threadgui, the BackgroundCalculator class extends the SwingWorker class and calculates a Fibonacci number in a separate thread. The PrimeCalculator extends the SwingWorker to find prime numbers in a separate thread.
JavaFX has its own built in classes for handling threads, including the Worker interface, and its subclasses Task and Service. This project demonstrates a JavaFX with and without threads, motivating their use in long-running tasks.