5.1 Running Threads
A
thread
with a little t is a separate, independent path
of execution in the virtual machine. A Thread with
a capital T is an instance of the
java.lang.Thread class. There is a one-to-one relationship
between threads executing in the virtual machine and
Thread objects constructed by the virtual machine.
Most of the time it's obvious from the context which
one is meant if the difference is really important. To start a new
thread running in the virtual machine, you construct an instance of
the Thread class and invoke its start() method, like this:
Thread t = new Thread( );Of course, this thread isn't very interesting
t.start( );
because it doesn't have anything to do. To give a
thread something to do, you either subclass the
Thread class and override its run() method, or implement the Runnable
interface and pass the Runnable object to the
Thread constructor. I generally prefer the second
option since it separates the task that the thread performs from the
thread itself more cleanly, but you will see both techniques used in
this book and elsewhere. In both cases, the key is the run() method, which has this signature:
public void run( )You're going to put all the work the thread does in
this one method. This method may invoke other methods; it may
construct other objects; it may even spawn other threads. However,
the thread starts here and it stops here. When the run() method completes, the thread dies. In essence, the
run( ) method is to a thread what the
main( ) method is to a traditional nonthreaded
program. A single-threaded program exits when the main() method returns. A multithreaded program exits when both
the main( ) method and the run() methods of all nondaemon threads return. (Daemon threads
perform background tasks such as garbage collection and
don't prevent the virtual machine from exiting.)
5.1.1 Subclassing Thread
For example, suppose you want to write a
program that calculates the Secure Hash Algorithm (SHA) digest for
many files. To a large extent, this program is I/O-bound; that is,
its speed is limited by the amount of time it takes to read the files
from the disk. If you write it as a standard program that processes
the files in series, the program's going to spend a
lot of time waiting for the hard drive to return the data. This is
characteristic of a lot of network programs: they have a tendency to
execute faster than the network can supply input. Consequently, they
spend a lot of time blocked. This is time that other threads could
use, either to process other input sources or to do something that
doesn't rely on slow input. (Not all threaded
programs share this characteristic. Sometimes, even if none of the
threads have a lot of spare time to allot to other threads,
it's simply easier to design a program by breaking
it into multiple threads that perform independent operations.) Example 5-1 is a subclass of Thread whose
run( ) method calculates an SHA message digest for
a specified file.
Example 5-1. FileDigestThread
import java.io.*;The main( ) method reads filenames from the
import java.security.*;
public class DigestThread extends Thread {
private File input;
public DigestThread(File input) {
this.input = input;
}
public void run( ) {
try {
FileInputStream in = new FileInputStream(input);
MessageDigest sha = MessageDigest.getInstance("SHA");
DigestInputStream din = new DigestInputStream(in, sha);
int b;
while ((b = din.read( )) != -1) ;
din.close( );
byte[] digest = sha.digest( );
StringBuffer result = new StringBuffer(input.toString( ));
result.append(": ");
for (int i = 0; i < digest.length; i++) {
result.append(digest[i] + " ");
}
System.out.println(result);
}
catch (IOException ex) {
System.err.println(ex);
}
catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
File f = new File(args[i]);
Thread t = new DigestThread(f);
t.start( );
}
}
}
command-line and starts a new DigestThread for
each one. The work of the thread is actually performed in the
run( ) method. Here, a
DigestInputStream reads the file. Then the
resulting digest is printed on System.out. Notice
that the entire output from this thread is first built in a local
StringBuffer variable result.
This is then printed on the console with one method invocation. The
more obvious path of printing the pieces one at a time using
System.out.print( ) is not taken.
There's a reason for that, which
we'll discuss soon.Since the signature of the run( ) method is fixed,
you can't pass arguments to it or return values from
it. Consequently, you need different ways to pass information into
the thread and get information out of it. The simplest way to pass
information in is to pass arguments to the constructor, which set
fields in the Thread subclass, as done here.Getting information out of a thread back into the original calling
thread is trickier because of the asynchronous nature of threads. Example 5-1 sidesteps that problem by never passing any information
back to the calling thread and simply printing the results on
System.out. Most of the time, however,
you'll want to pass the information to other parts
of the program. You can store the result of the calculation in a
field and provide a getter method to return the value of that field.
However, how do you know when the calculation of that value is
complete? What do you return if somebody calls the getter method
before the value has been calculated? This is quite tricky, and
we'll discuss it more later in this chapter.If you subclass
Thread, you should override run() and nothing else! The various other
methods of the Thread class, start(), stop( ), interrupt(
), join( ), sleep( ),
and so on, all have very specific semantics and interactions with the
virtual machine that are difficult to reproduce in your own code. You
should override run( ) and provide additional
constructors and other methods as necessary, but you should not
replace any of the other standard Thread
methods.
5.1.2 Implementing the Runnable Interface
One way to avoid overriding the standard
Thread methods is not to subclass
Thread. Instead, write the task you want the
thread to perform as an instance of the Runnable
interface. This interface declares the run( )
method, exactly the same as the Thread class:
public void run( )Other than this method, which any class implementing this interface
must provide, you are completely free to create any other methods
with any other names you choose, all without any possibility of
unintentionally interfering with the behavior of the thread. This
also allows you to place the thread's task in a
subclass of some other class, such as Applet or
HTTPServlet. To start a thread that performs the
Runnable's task, pass the
Runnable object to the Thread
constructor. For example:
Thread t = new Thread(myRunnableObject);It's easy to recast most problems that subclass
t.start( );
Thread into Runnable forms. Example 5-2 demonstrates by rewriting Example 5-1 to use the
Runnable interface rather than subclassing
Thread. Aside from the name change, the only
modifications that are necessary are changing extends
Thread to implements
Runnable and passing a
DigestRunnable object to the
Thread constructor in the main() method. The essential logic of the program is unchanged.
Example 5-2. DigestRunnable
import java.io.*;There's no strong reason to prefer implementing
import java.security.*;
public class DigestRunnable implements Runnable {
private File input;
public DigestRunnable(File input) {
this.input = input;
}
public void run( ) {
try {
FileInputStream in = new FileInputStream(input);
MessageDigest sha = MessageDigest.getInstance("SHA");
DigestInputStream din = new DigestInputStream(in, sha);
int b;
while ((b = din.read( )) != -1) ;
din.close( );
byte[] digest = sha.digest( );
StringBuffer result = new StringBuffer(input.toString( ));
result.append(": ");
for (int i = 0; i < digest.length; i++) {
result.append(digest[i] + " ");
}
System.out.println(result);
}
catch (IOException ex) {
System.err.println(ex);
}
catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
File f = new File(args[i]);
DigestRunnable dr = new DigestRunnable(f);
Thread t = new Thread(dr);
t.start( );
}
}
}
Runnable to extending Thread or
vice versa in the general case. In a few special cases, such as Example 5-14 later in this chapter, it may be useful to
invoke some instance methods of the Thread class
from within the constructor for each Thread
object. This requires using a subclass. In some specific cases, it
may be necessary to place the run( ) method in a
class that extends another class, such as Applet,
in which case the Runnable interface is essential.
Finally, some object-oriented purists argue that the task that a
thread undertakes is not really a kind of Thread,
and therefore should be placed in a separate class or interface such
as Runnable rather than in a subclass of
Thread. I half agree with them, although I
don't think the argument is as strong as
it's sometimes made out to be. Consequently,
I'll mostly use the Runnable
interface in this book, but you should feel free to do whatever seems
most convenient.