Java’s multithreading system is built upon the Thread class, its methods, and its
companion interface, Runnable. Thread encapsulates a thread of execution. Since
you can’t directly refer to the state of a running thread, you will deal with it
through its proxy, the Thread instance that spawned it. To create a new thread, your
program will either extend Thread or implement the Runnable interface.
The Thread class defines several methods that help manage threads. The ones
that will be used in this chapter are shown here:
Method
getName Obtain a thread’s name. getPriority Obtain a thread’s priority.
isAlive Determine if a thread is still running.
join Wait for a thread to terminate.
run Entry point for the thread.
sleep Suspend a thread for a period of time.
start Start a thread by calling its run method.
Thus far, all the examples in this book have used a single thread of execution. The
remainder of this chapter explains how to use Thread and Runnable to create and
manage threads, beginning with the one thread that all Java programs have: the
main thread
The Main Thread
When a Java program starts up, one thread begins running immediately. This is
usually called the main thread of your program, because it is the one that is executed
when your program begins. The main thread is important for two reasons:
■ It is the thread from which other “child” threads will be spawned.
■ Often it must be the last thread to finish execution because it performs various
shutdown actions.
Although the main thread is created automatically when your program is started, it
can be controlled through a Thread object. To do so, you must obtain a reference to it
by calling the method currentThread( ), which is a public static member of Thread. Its
general form is shown here:
static Thread currentThread( )
This method returns a reference to the thread in which it is called. Once you have a
reference to the main thread, you can control it just like any other thread.
Let’s begin by reviewing the following example:
// Controlling the main Thread.
class CurrentThreadDemo {
public static void main(String args[]) {
Thread t = Thread.currentThread();
System.out.println("Current thread: " + t);
// change the name of the thread
t.setName("My Thread");
System.out.println("After name change: " + t);
try {
for(int n = 5; n > 0; n--) {
System.out.println(n);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted");
}
}
}
In this program, a reference to the current thread (the main thread, in this case) is
obtained by calling currentThread( ), and this reference is stored in the local variable t.
Next, the program displays information about the thread. The program then calls
setName( ) to change the internal name of the thread. Information about the thread is
then redisplayed. Next, a loop counts down from five, pausing one second between
each line. The pause is accomplished by the sleep( ) method. The argument to sleep( )
specifies the delay period in milliseconds. Notice the try/catch block around this loop.
The sleep( ) method in Thread might throw an InterruptedException. This would
happen if some other thread wanted to interrupt this sleeping one. This example just
prints a message if it gets interrupted. In a real program, you would need to handle
this differently.
Here is the output generated by this program:
Current thread: Thread[main,5,main]
After name change: Thread[My Thread,5,main]
5
4
3
2
1
Notice the output produced when t is used as an argument to println( ). This displays,
in order: the name of the thread, its priority, and the name of its group. By default, the
name of the main thread is main. Its priority is 5, which is the default value, and main
is also the name of the group of threads to which this thread belongs. A thread group is
a data structure that controls the state of a collection of threads as a whole. This process
is managed by the particular run-time environment and is not discussed in detail here.
After the name of the thread is changed, t is again output. This time, the new name of
the thread is displayed.
Let’s look more closely at the methods defined by Thread that are used in the
program. The sleep( ) method causes the thread from which it is called to suspend
execution for the specified period of milliseconds. Its general form is shown here:
static void sleep(long milliseconds) throws InterruptedException
The number of milliseconds to suspend is specified in milliseconds. This method may throw an InterruptedException.
The sleep( ) method has a second form, shown next, which allows you to specify
the period in terms of milliseconds and nanoseconds:
static void sleep(long milliseconds, int nanoseconds) throws InterruptedException
This second form is useful only in environments that allow timing periods as short
as nanoseconds.
As the preceding program shows, you can set the name of a thread by using
setName( ). You can obtain the name of a thread by calling getName( ) (but note
that this procedure is not shown in the program). These methods are members
of the Thread class and are declared like this:
final void setName(String threadName)
final String getName( )
Here, threadName specifies the name of the thread.
Creating a Thread
In the most general sense, you create a thread by instantiating an object of type Thread.
Java defines two ways in which this can be accomplished:
■ You can implement the Runnable interface.
■ You can extend the Thread class, itself.
The following two sections look at each method, in turn.
Implementing Runnable
The easiest way to create a thread is to create a class that implements the Runnable
interface. Runnable abstracts a unit of executable code. You can construct a thread on
any object that implements Runnable. To implement Runnable, a class need only
implement a single method called run( ), which is declared like this:
public 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 can. 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.
After you create a class that implements Runnable, you will instantiate an object of
type Thread from within that class. Thread defines several constructors. The one that
we will use is shown here:
Thread(Runnable threadOb, String threadName)
In this constructor, threadOb is an instance of a class that implements the Runnable
interface. This defines where execution of the thread will begin. The name of the new
thread is specified by threadName.
After the new thread is created, it will not start running until you call its start( )
method, which is declared within Thread. In essence, start( ) executes a call to run( ).
The start( ) method is shown here:
void start( )
Here is an example that creates a new thread and starts it running:
// Create a second thread.
class NewThread implements Runnable {
Thread t;
NewThread() {
// Create a new, second thread
t = new Thread(this, "Demo Thread");
System.out.println("Child thread: " + t);
t.start(); // Start the thread
}
// This is the entry point for the second thread.
public void run() {
try {
for(int i = 5; i > 0; i--) {
System.out.println("Child Thread: " + i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("Child interrupted.");
}
System.out.println("Exiting child thread.");
}
}
class ThreadDemo {
public static void main(String args[]) {
new NewThread(); // create a new thread
try {
for(int i = 5; i > 0; i--) {
System.out.println("Main Thread: " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread exiting.");
}
}
Inside NewThread’s constructor, a new Thread object is created by the following
statement:
t = new Thread(this, "Demo Thread");
Passing this as the first argument indicates that you want the new thread to call the
run( ) method on this object. Next, start( ) is called, which starts the thread of execution
beginning at the run( ) method. This causes the child thread’s for loop to begin. After
calling start( ), NewThread’s constructor returns to main( ). When the main thread
resumes, it enters its for loop. Both threads continue running, sharing the CPU, until
their loops finish.
The output produced by this program is as follows:
Child thread: Thread[Demo Thread,5,main]
Main Thread: 5
Child Thread: 5
Child Thread: 4
Main Thread: 4
Child Thread: 3
Child Thread: 2
Main Thread: 3
Child Thread: 1
Exiting child thread.
Main Thread: 2
Main Thread: 1
Main thread exiting.
As mentioned earlier, in a multithreaded program, often the main thread must
be the last thread to finish running. In fact, for some older JVMs, if the main thread
finishes before a child thread has completed, then the Java run-time system may “hang.” The preceding program ensures that the main thread finishes last, because
the main thread sleeps for 1,000 milliseconds between iterations, but the child thread
sleeps for only 500 milliseconds. This causes the child thread to terminate earlier than
the main thread. Shortly, you will see a better way to wait for a thread to finish.
Extending Thread
The second way to create a thread is to create a new class that extends Thread,
and then to create an instance of that class. The extending class must override the
run( ) method, which is the entry point for the new thread. It must also call start( )
to begin execution of the new thread. Here is the preceding program rewritten to
extend Thread:
// Create a second thread by extending Thread
class NewThread extends Thread {
NewThread() {
// Create a new, second thread
super("Demo Thread");
System.out.println("Child thread: " + this);
start(); // Start the thread
}
// This is the entry point for the second thread.
public void run() {
try {
for(int i = 5; i > 0; i--) {
System.out.println("Child Thread: " + i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("Child interrupted.");
}
System.out.println("Exiting child thread.");
}
}
class ExtendThread {
public static void main(String args[]) {
new NewThread(); // create a new thread
try {
for(int i = 5; i > 0; i--) {
System.out.println("Main Thread: " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread exiting.");
}
}
This program generates the same output as the preceding version. As you can see, the
child thread is created by instantiating an object of NewThread, which is derived
from Thread.
Notice the call to super( ) inside NewThread. This invokes the following form of
the Thread constructor:
public Thread(String threadName)
Here, threadName specifies the name of the thread.
Choosing an Approach
At this point, you might be wondering why Java has two ways to create child threads,
and which approach is better. The answers to these questions turn on the same point.
The Thread class defines several methods that can be overridden by a derived class.
Of these methods, the only one that must be overridden is run( ). This is, of course, the
same method required when you implement Runnable. Many Java programmers feel
that classes should be extended only when they are being enhanced or modified in
some way. So, if you will not be overriding any of Thread’s other methods, it is
probably best simply to implement Runnable. This is up to you, of course. However,
throughout the rest of this chapter, we will create threads by using classes that
implement Runnable.
Creating Multiple Threads
So far, you have been using only two threads: the main thread and one child thread.
However, your program can spawn as many threads as it needs. For example, the
following program creates three child threads:
// Create multiple threads.
class NewThread implements Runnable {
String name; // name of thread
Thread t;
NewThread(String threadname) {
name = threadname;
t = new Thread(this, name);
System.out.println("New thread: " + t);
t.start(); // Start the thread
}
// This is the entry point for thread.
public void run() {
try {
for(int i = 5; i > 0; i--) {
System.out.println(name + ": " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println(name + "Interrupted");
}
System.out.println(name + " exiting.");
}
}
class MultiThreadDemo {
public static void main(String args[]) {
new NewThread("One"); // start threads
new NewThread("Two");
new NewThread("Three");
try {
// wait for other threads to end
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("Main thread Interrupted");
}
System.out.println("Main thread exiting.");
}
}
The output from this program is shown here:
New thread: Thread[One,5,main]
New thread: Thread[Two,5,main]
New thread: Thread[Three,5,main]
One: 5
Two: 5
Three: 5
One: 4
Two: 4
Three: 4
One: 3
Three: 3
Two: 3
One: 2
Three: 2
Two: 2
One: 1
Three: 1
Two: 1
One exiting.
Two exiting.
Three exiting.
Main thread exiting.
As you can see, once started, all three child threads share the CPU. Notice the call to
sleep(10000) in main( ). This causes the main thread to sleep for ten seconds and
ensures that it will finish last.
Synchronization
When two or more threads need access to a shared resource, they need some way to
ensure that the resource will be used by only one thread at a time. The process by
which this is achieved is called synchronization. As you will see, Java provides unique,
language-level support for it.
Key to synchronization is the concept of the monitor (also called a semaphore). A
monitor is an object that is used as a mutually exclusive lock, or mutex. Only one thread
can own a monitor at a given time. When a thread acquires a lock, it is said to have
entered the monitor. All other threads attempting to enter the locked monitor will be
suspended until the first thread exits the monitor. These other threads are said to
be waiting for the monitor. A thread that owns a monitor can reenter the same monitor
if it so desires.
Using Synchronized Methods
Synchronization is easy in Java, because all objects have their own implicit monitor
associated with them. To enter an object’s monitor, just call a method that has been
modified with the synchronized keyword. While a thread is inside a synchronized
method, all other threads that try to call it (or any other synchronized method)
on the same instance have to wait. To exit the monitor and relinquish control of
the object to the next waiting thread, the owner of the monitor simply returns
from the synchronized method.To understand the need for synchronization, let’s begin with a simple example that
does not use it—but should. The following program has three simple classes. The first
one, Callme, has a single method named call( ). The call( ) method takes a String
parameter called msg. This method tries to print the msg string inside of square
brackets. The interesting thing to notice is that after call( ) prints the opening bracket
and the msg string, it calls Thread.sleep(1000), which pauses the current thread for
one second.
The constructor of the next class, Caller, takes a reference to an instance of the
Callme class and a String, which are stored in target and msg, respectively. The
constructor also creates a new thread that will call this object’s run( ) method. The
thread is started immediately. The run( ) method of Caller calls the call( ) method on
the target instance of Callme, passing in the msg string. Finally, the Synch class starts
by creating a single instance of Callme, and three instances of Caller, each with a unique
message string. The same instance of Callme is passed to each Caller.
// This program is not synchronized.
class Callme {
void call(String msg) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
class Caller implements Runnable {
String msg;
Callme target;
Thread t;
public Caller(Callme targ, String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
public void run() {
target.call(msg);
}
}
class Synch {
public static void main(String args[]) {
Callme target = new Callme();
Caller ob1 = new Caller(target, "Hello");
Caller ob2 = new Caller(target, "Synchronized");
Caller ob3 = new Caller(target, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
}
}
Here is the output produced by this program:
[Hello[Synchronized[World]
]
]
As you can see, by calling sleep( ), the call( ) method allows execution to switch to
another thread. This results in the mixed-up output of the three message strings. In
this program, nothing exists to stop all three threads from calling the same method, on
the same object, at the same time. This is known as a race condition, because the three threads are racing each other to complete the method. This example used sleep( ) to
make the effects repeatable and obvious. In most situations, a race condition is more
subtle and less predictable, because you can’t be sure when the context switch will
occur. This can cause a program to run right one time and wrong the next.
To fix the preceding program, you must serialize access to call( ). That is, you must
restrict its access to only one thread at a time. To do this, you simply need to precede
call( )’s definition with the keyword synchronized, as shown here:
class Callme {
synchronized void call(String msg) {
...
This prevents other threads from entering call( ) while another thread is using it.
After synchronized has been added to call( ), the output of the program is as follows:
[Hello]
[Synchronized]
[World]
Any time that you have a method, or group of methods, that manipulates the
internal state of an object in a multithreaded situation, you should use the synchronized
keyword to guard the state from race conditions. Remember, once a thread enters any
synchronized method on an instance, no other thread can enter any other synchronized
method on the same instance. However, nonsynchronized methods on that instance
will continue to be callable.
The synchronized Statement
While creating synchronized methods within classes that you create is an easy and
effective means of achieving synchronization, it will not work in all cases. To understand
why, consider the following. Imagine that you want to synchronize access to objects of
a class that was not designed for multithreaded access. That is, the class does not use
synchronized methods. Further, this class was not created by you, but by a third party,
and you do not have access to the source code. Thus, you can’t add synchronized to
the appropriate methods within the class. How can access to an object of this class be
synchronized? Fortunately, the solution to this problem is quite easy: You simply put
calls to the methods defined by this class inside a synchronized block.
This is the general form of the synchronized statement:
synchronized(object) {
// statements to be synchronized
}
Here, object is a reference to the object being synchronized. A synchronized block
ensures that a call to a method that is a member of object occurs only after the current
thread has successfully entered object’s monitor.
Here is an alternative version of the preceding example, using a synchronized block
within the run( ) method:
// This program uses a synchronized block.
class Callme {
void call(String msg) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
class Caller implements Runnable {
String msg;
Callme target;
Thread t;
public Caller(Callme targ, String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
// synchronize calls to call()
public void run() {
synchronized(target) { // synchronized block
target.call(msg);
}
}
}
class Synch1 {
public static void main(String args[]) {
Callme target = new Callme();
Caller ob1 = new Caller(target, "Hello");
Caller ob2 = new Caller(target, "Synchronized");
Caller ob3 = new Caller(target, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
}
}
Here, the call( ) method is not modified by synchronized. Instead, the synchronized
statement is used inside Caller’s run( ) method. This causes the same correct output as
the preceding example, because each thread waits for the prior one to finish before
proceeding.