22.3 Creating Threads
A thread in Java is represented by an object of the java.lang.Thread class. Every thread in Java is created and controlled by an object of this class. Often the thread (of execution) and its associated Thread object are thought of as being synonymous. Implementing the task performed by a thread is achieved in one of two ways:
- Implementing the functional interface java.lang.Runnable
- Extending the java.lang.Thread class
In both cases, thread creation and management is controlled by the application. Chapter 23, p. 1419, discusses high-level concurrency features that abstract the drudgery of thread creation and management, facilitating the building of massively concurrent applications.
Implementing the Runnable Interface
The Runnable functional interface has the following specification, comprising a single abstract method declaration:
@FunctionalInterface
public interface Runnable {
void run();
}
A thread, which is created based on an object that implements the Runnable interface, will execute the code defined in the public method run(). In other words, the code in the run() method defines an independent path of execution and thereby the entry and the exits for the thread. A thread ends when the run() method ends, either by normal completion or by throwing an uncaught exception. Note that the method run() does not return a value, does not take any parameters, and does not throw any checked exceptions.
The procedure for creating threads based on the Runnable interface is as follows:
- Implement the Runnable interface, providing the run() method that will be executed by the thread. This can be done by providing a concrete or an anonymous class that implements the Runnable() interface. Since the interface is a functional interface, it can also be implemented by a lambda expression, typically for simple tasks.
- An object of the Thread class is created by passing the Runnable implementation from step 1 as an argument in the Thread constructor call. The Thread object now has a Runnable object that implements the run() method.
- The start() method is invoked on the Thread object created in step 2. The start() method returns immediately after a thread has been spawned. In other words, the call to the start() method is asynchronous.
When the thread, represented by the Thread object on which the start() method was invoked, gets to run, it executes the run() method of the Runnable object. This sequence of events is illustrated in Figure 22.2.
In the following code, the functional interface Runnable is implemented by a lambda expression that is passed to the Thread object. The code will create a thread and start the thread. When the thread gets to run, it will execute the print statement.
Thread thread = new Thread(
() -> System.out.println(“Harmonious threads create beautiful applications.”)
);
thread.start();
Figure 22.2 Spawning Threads Using a Runnable Object
The following is a summary of selected constructors and methods from the java.lang.Thread class:
Thread(Runnable threadTarget)
Thread(Runnable threadTarget, String threadName)
The argument threadTarget is the object whose run() method will be executed when the thread is started. The argument threadName can be specified to give an explicit name for the thread, rather than an automatically generated one.
void start()
Spawns a new thread—that is, the new thread will begin execution as a child thread of the current thread. The spawning is done asynchronously as the call to this method returns immediately. It throws an IllegalThreadStateException if the thread is already started or it has already completed execution.
void run()
The Thread class implements the Runnable interface by providing an implementation of the run() method. This implementation in the Thread class does nothing and returns. Subclasses of the Thread class should override this method. If the current thread is created using a separate Runnable object, the run() method of this Runnable object is called.
static Thread currentThread()
Returns a reference to the Thread object of the currently executing thread.
final String getName()
final void setName(String name)
The first method returns the name of the thread. The second one sets the thread’s name to the specified argument.
final void setDaemon(boolean flag)
final boolean isDaemon()
The first method sets the status of the thread either as a daemon thread or as a normal thread (p. 1377), depending on whether the argument is true or false, respectively. The status should be set before the thread is started. The second method returns true if the thread is a daemon thread; otherwise, it returns false.
A slightly more elaborate example of creating a thread is presented in Example 22.1. The class Counter implements the Runnable interface. At (1), the class defines the run() method that constitutes the code to be executed in a thread. The while loop in the run() method executes as long as the current value is less than 5. In each iteration of the while loop, the old value and the new incremented value of the counter is printed, as shown at (3). Also, in each iteration, the thread will sleep for 500 milliseconds, as shown at (4). While it is sleeping, other threads may run (p. 1395).
The static method currentThread() in the Thread class can be used to obtain a reference to the Thread object associated with the current thread. We can call the get-Name() method on the current thread to obtain its name. An example of its usage, shown at (2), prints the name of the thread executing the run() method. Another example of its usage, shown at (5), prints the name of the thread executing the main() method.
The Client class in Example 22.1 uses the Counter class. It creates an object of the class Counter at (6). This Counter object is passed to two Thread objects at (7). The threads are started at (8). In other words, we have two threads that will increment the value in the same Counter object.
Both Thread A and Thread B are child threads of the main thread. They inherit the normal-thread status from the main thread (p. 1377). The output shows that the main thread finishes executing before the child threads. However, the program will continue running until the child threads have completed their execution of the run() method in the Counter object.
If a thread has been started or if it has completed, invoking the start() method again on the thread results in an IllegalThreadStateException, as shown at (9). Note that this does not terminate the thread that has already been started. If the thread has completed, a new thread must be created before the start() method can be called.
Since thread scheduling is not predictable (p. 1386) and Example 22.1 does not enforce any synchronization between the two threads in accessing the current value in the Counter object, the output shown may vary. The output from the two child threads is interspersed. The output also shows that the counter was incremented by 2 when Thread B executed for the first time! This is an example of thread interference. The challenge in multithreaded programming is to synchronize how shared data is accessed by the threads in the application.
Example 22.1 Implementing the Runnable Interface
public class Counter implements Runnable {
private int currentValue = 0;
@Override
public void run() { // (1) Thread entry point
String threadName = Thread.currentThread().getName(); // (2)
while (currentValue < 5) {
System.out.printf(“%s: old:%s new:%s%n”,
threadName, // (3) Print thread name,
this.currentValue, // old value,
++this.currentValue); // new incremented value.
try {
Thread.sleep(500); // (4) Current thread sleeps.
} catch (InterruptedException e) {
System.out.println(threadName + ” interrupted.”);
}
}
System.out.println(“Exiting ” + threadName);
}
}
public class Client {
public static void main(String[] args) {
String threadName = Thread.currentThread().getName(); // (5) main thread
System.out.println(“Method main() runs in thread ” + threadName);
// Create a Counter object: // (6)
Counter counter = new Counter();
// Create two threads with the same Counter: // (7)
Thread threadA = new Thread(counter, “Thread A”);
Thread threadB = new Thread(counter, “Thread B”);
// Start the two threads: // (8)
System.out.println(“Starting ” + threadA.getName());
threadA.start();
// threadA.start(); // (9) IllegalThreadStateException
System.out.println(“Starting ” + threadB.getName());
threadB.start();
System.out.println(“Exiting Thread ” + threadName); // (10)
}
}
Probable output from the program:
Method main() runs in thread main
Starting Thread A
Starting Thread B
Exiting Thread main
Thread B: old:0 new:2
Thread A: old:0 new:1
Thread B: old:2 new:3
Thread A: old:3 new:4
Thread B: old:4 new:5
Exiting Thread A
Exiting Thread B