Multithreading Fundamentals in Java

Among the defining characteristics of Java is its built-in support for multithreaded programming. This support, which has been present in Java from the start, is provided by the Thread class, the Runnable interface, several methods supplied by Object, and the synchronized keyword. Multithreading enables you to write programs that contain two or more separate paths of execution that can execute concurrently. Each path of execution is called a thread. Through the careful use of multithreading, you can create programs that make efficient use of system resources and maintain a responsive
user interface.
Because multiple threads can interact in ways that are not always intuitive, adding level of complexity that is not present in a single-threaded program, some programmers avoid multithreading whenever possible. However, the modern programming world is moving toward more use of multithreading, not less. Highly parallel architectures are becoming the norm. Simply put, multithreading will continue to play a critical part in many (perhaps most) real-world applications of Java.

Multithreading Fundamentals

At its core, multithreading is a form of multitasking. There are two distinct types of multitasking: process-based and thread-based. It is important to differentiate between the two. As it relates to this discussion, a process is, in essence, a program that is executing. Thus, process-based multitasking is the feature that allows your computer to run two or more programs concurrently. For example, it is process-based multitasking that allows you to download a file at the same time you are compiling a program or sorting a database. In process-based multitasking, a program is the smallest unit of code that can be dispatched by the scheduler.
In a thread-based multitasking environment, the thread is the smallest unit of dispatchable code. Because a program can contain more than one thread, a single program can use multiple threads to perform two or more tasks at once. For instance, a browser can begin rendering a Web page while it is still downloading the remainder of the page. This is possible because each action is performed by a separate thread. Although Java programs make use of processbased multitasking environments, process-based multitasking is not under the direct control of Java. Multithreaded multitasking is.
All processes have at least one thread of execution, which is called the main thread, because it is the one that is executed when a program begins. From the main thread, you can create other threads. These other threads can also create threads, and so on.
Multithreading is important to Java for two main reasons. First, multithreading enables you to write very efficient programs because it lets you utilize the idle time that is present in most programs. Most I/O devices, whether they be network ports, disk drives, or the keyboard, are much slower than the CPU. Thus, a program will often spend a majority of its execution time waiting to send or receive information to or from a device. By using multithreading, your program can execute another task during this idle time. For example, while one part of your program is sending a file over the Internet, another part can be
handling user interaction (such as mouse clicks or button presses), and still another can be buffering the next block of data to send.
The second reason that multithreading is important to Java relates to Java’s eventhandling model. A program (such as an applet) must respond quickly to an event and then return. An event handler must not retain control of the CPU for an extended period of time.
If it does, other events will not be handled in a timely fashion. This will make an application appear sluggish. It is also possible that an event will be missed. Therefore, if an event requires some extended action, then it must be performed by a separate thread.
A thread can be in one of several states. It can be running. It can be ready to run as soon as it gets CPU time. A running thread can be suspended, which is a temporary halt to its execution. It can later be resumed. A thread can be blocked when waiting for a resource. A thread can be terminated, in which case its execution ends and cannot be resumed.
Along with thread-based multitasking comes the need for synchronization, which allows the execution of threads to be coordinated in certain well-defined ways. Java has extensive, integrated support for synchronization, which is achieved through the use of a monitor, which all objects have, and the synchronized keyword. Thus, all objects can be synchronized.
Two or more threads can communicate with each other through methods that are defined by Object. These methods are wait( ), notify( ), and notifyAll( ). They enable one thread to wait on another. For example, if one thread is using a shared resource, then another thread must wait until the first thread has finished. The waiting thread can resume execution when the first thread notifies it that the resource is now available.
There are two basic types of threads: user and daemon. A user thread is the type of thread created by default. For example, the main thread is a user thread. In general, a program continues to execute as long as there is at least one active user thread. It is possible to change the status of a thread to daemon. Daemon threads are automatically terminated when all nondaemon threads have terminated. Thus, they are subordinate to user threads.
Threads can be part of a group. A thread group enables you to manage related threads collectively. For example, you can obtain an array of the threads in the group.
Java’s multithreading system is built upon the Thread class and its companion interface, Runnable. Thread encapsulates a thread of execution. To create a new thread, your program will either implement the Runnable interface or extend Thread. Both Runnable and Thread are packaged in java.lang. Thus, they are automatically available to all programs.

The Runnable Interface

The java.lang.Runnable interface abstracts a unit of executable code. You can construct a thread on any object that implements the Runnable interface. Therefore, any class that you intend to run in a separate thread must implement Runnable. Runnable defines only one method called run( ), which is declared like this:
  1. void run()
Inside run( ), you will define the code that constitutes the new thread. It is important to understand that run( ) can call other methods, use other classes, and declare variables just like the main thread. The only difference is that run( ) establishes the entry point for another, concurrent thread of execution within your program. This thread will end when run( ) returns.
Once you have created an instance of a class that implements Runnable, you create a thread by constructing an object of type Thread, passing in the Runnable instance. To start the thread running, you will call start( ) on the Thread object, as described in the next section.

The Thread Class

The Thread class encapsulates a thread. It is packaged in java.lang and implements the Runnable interface. Therefore, a second way to create a thread is to extend Thread and override the run( ) method. Thread also defines several methods that help manage threads. Here are the ones used in this chapter:
The Methods Defiend by InputStream
Method Description
static Thread currentThread( ) Returns a reference to a Thread object that represents the invoking thread.
long getID( ) Returns a thread’s ID.
final String getName( ) Obtains a thread’s name.
final int getPriority( ) Obtains a thread’s priority.
Thread.State getState( ) Returns the current state of the thread.
static boolean holdsLock(Object obj) Returns true if the invoking thread holds the lock on obj.
void interrupt( ) Interrupts a thread.
static boolean interrupted( ) Returns true if the invoking thread has been interrupted.
final boolean isAlive( ) Determines whether a thread is still running.
final boolean isDaemon( ) Returns true if the invoking thread is a daemon thread.
boolean isInterrupted( ) Returns true if the thread on which it is called has been interrupted.
final void join( ) Waits for a thread to terminate.
void run( ) Entry point for the thread.
final void setDaemon(boolean how) If how is true, the invoking thread is set to daemon status.
final void setName(String thrdName) Sets a thread’s name to thrdName.
final void setPriority(int level) Sets a thread’s priority to level.
static void sleep(long milliseconds) Suspends a thread for a specified period of milliseconds.
void start( ) Starts a thread by calling its run( ) method.
static void yield( ) Yields the CPU to another thread.
Pay special attention to the start( ) method. After an instance of Thread has been created, call start( ) to begin execution of the thread. The start( ) method calls run( ), which is the method defined by Runnable that contains the code to be executed in the thread. This process is described in detail in the following recipes.
Another method of special interest is sleep( ). It suspends execution of a thread for a specified period of time. When a thread sleeps, another thread can execute until the sleeping thread awakes and resumes execution. Several examples in this chapter use sleep( ) to demonstrate the effects of multiple threads.
Thread defines two sets of constructors, one for constructing a thread on a separate instance of Runnable and the other for constructing a thread on classes that extend Thread. Here are the constructors that take a separate instance of Runnable:
  1. Thread(Runnable thrdOb)
  2. Thread(Runnable thrdOb, String thrdName)
  3. Thread(ThreadGroup thrdGroup, Runnable thrdObj)
  4. Thread(ThreadGroup thrdGroup, Runnable thrdObj, String thrdName)
Here, thrdObj is a reference to an instance of a class that implements Runnable. This object’s run( ) method contains the code that will be executed as the new thread. The name of thread is passed in thrdName. If no name is specified (or the name is null), then a name is supplied automatically by the JVM. The thread group to which the thread belongs (if any) is passed via thrdGroup. If the thread group is not specified, then the thread group is determined by the security manager (if there is one) or is set to the same group as the invoking thread.
Here are the constructors that create a thread for classes that extend Thread:
  1. Thread( )
  2. Thread(String thrdName)
  3. Thread(ThreadGroup thrdGroup, String thrdName)
The first constructor creates a thread that uses the default name and thread group, as described already. The second lets you specify the name. The third lets you specify the thread group and the name.
For both sets of constructors, the thread will be created as a user thread unless the creating thread is a daemon thread. In this case, the thread will be created as a daemon thread.
There is another Thread constructor that lets you specify a stack size for the thread. However, because of differences in execution environments, the API documentation states that "extreme care should be exercised in its use."

Written by