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( ); t.start( ); 

Of course, this thread isn't very interesting 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.*; 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( );     }      } } 

The main( ) method reads filenames from the 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); t.start( ); 

It's easy to recast most problems that subclass 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.*; 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( );     }      } } 

There's no strong reason to prefer implementing 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 .



Java Network Programming
Java Network Programming, Third Edition
ISBN: 0596007213
EAN: 2147483647
Year: 2003
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net