카테고리 없음
Threads
Triton
2010. 12. 29. 14:56
Running Threads
Subclassing Thread
The main() method reads a directory and saves into an array. Then it starts a new DigestThread for each one. The work of the thread is actually performed in 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.
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 class, as done here. If you subclass Thread, you should override run() and nothing else.
Implementing the Runnable Interface
One way to avoid overriding the standard Thread methods is not to subclass Thread. Instead, you can 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.
There's no strong reason to prefer implementing Runnable to extending Thread or vice versa in the general case.
Returning Information from a Thread
One of the hardest things for programmers accustomed to traditional, single threaded procedural models to grasp when moving to a multithreaded environment is how to return information from a thread. Getting infromation out of a finished thread is one of the most commonly misunderstood aspects of multithreaded programming. The run() method and the start() method don't return any values.
while((b = din.read() )!= -1);
din.close();
digest = sha.digest();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public byte[] getDigest() {
return digest;
}
}
_M#]
// Now print the result
StringBuffer result = new StringBuffer(f.toString());
result.append(": ");
for (int j = 0; j < digest.length; j++) {
result.append(digest[j] + " ");
}
System.out.println(result);
}
}
}
The problem of this code is that the main program gets the digest and uses it before the thread has had a chance to initialize it. Although this flow of control would work in a single-threaded program in which dr.start() simply invoked the run() method in the same thread, that's not what happens here. The calculations that dr.start() kicks off may or may not finish before the main() method reaches the call to dr.getDigest(). If they haven't finished, then dr.getDigest() returns null, and the first attempt to access digest throws a NullPointerException.
Race Conditions
Whether this code works is completely dependent on whether every one of the ReturnDigest threads finishes before its getDigest() method is called. If the first for loop is too fast, and the second for loop is entered before the threads spawned by the first loop start finishing, then you're back where we started. Whether you get the correct results, or this exception, depends on many factors, including how many threads you're spawning, the relative speeds of the CPU and disk on the system where this is run, and the algorithm the Java virtual machine uses to allot time to different threads.
Race Condition - Getting the correct result depends on the relative speeds of different threads, and you can't control those.
Polling
The solution most novices adopt is to have the getter method return a flag value until the result field is set. Then the main thread periodically polls the getter method to see whether it's returning something other than the flag value. In this example, that would mean repeatedly testing whether the digest is null and using it only if it isn't.
Callbacks
The infinite loop that repeatedly polls each ReturnDigest object to see whether it's finished can be eliminated. The trick is that rather than having the main program repeatedly ask each ReturnDigest thread whether it's finished, we let the thread tell the main program when it's finished. It does this by invoking a method in the main class that started it. This is called a callback because the thread calls back its creator when it's done. This way, the main program can go to sleep while waiting for the threads to finish and not steal time from the running threads.
When the thread's run() method is nearly done, the last thing it does it invoke a known method in the main program with the result. Rather than the main program asking each thread for the answer, each thread tells the main program answer.
At the end of the run() method, it passes off the digest to the static CallbackDigestUserInterface.receiveDigest() method in the class that originally started the thread.
The CallbackDigestUserInerface provides the main() method. However, unlike the main() methods in the other variations of this program in this chapter, this one only starts the threads for the files. It does not attempt to actually read, print out, or in any other way work with the results of calculation. That is handled by a separate method, reciveDigest(). This method is not invoked by the main() method or by any method that can be reached by following the flow of control from the main() method. Instead, it is invoked by each thread separately. In effect, it runs inside the digesting threads rather than inside the main thread of execution. These two use static methods for the callback so that CallbackDigest needs to know only the name of the method in CallbackDigestUserInterface to call.
However, it's not much harder (and considerably more common) to call back to instance method. In this case, the class making the callback must have a reference to the object it's calling back. When the run() method is nearly done, the last thing it does is invoke the instance method on the callback object to pass along the result.
It now has one additional field, a CallbackDigestUserInterface object called callback. At the end of the run() method, the digest is passed to callback's receiveDigest() method. The CallbackDigestUserInterface object itselfe is set in the constructor.
The CallbackDigestUserInterface calss holds the main() method as well as the receiveDigest() method used to handle an incoming digest.
Using instance methods instead of static methods for callbacks is a little more complicated but has a number of advantages. First, each instance of the main class, InstanceCallbackDigestUserInterface in this example, maps to exactly one file and can keep track of information about that file in a natural way without needing extra data structures. Furthermore, the instance can easily recalculate the digest for a particular file if necessary. This proves a lot more flexible. However, there is one caveat. Notice the addition of a start() method. You might logically think that this belongs in a constructor. However, starting threads in a constructor is dangerous, especially threads that will call back to the originating object. There's a race condition here that may allow the new thread to call back before the constructor is finished and the object is fully initialized.
The first advantage of the callback scheme over the polling scheme is that it doesn't waste so many CPU cycles on polling. But a much more important advantage is that callbacks are more flexible and can handle more complicated situations involving many more threads, objects, and classes. For instance, if more than one object is interested in the result of the thread's calculation, the thread can keep a list of objects to call back. Particular objects can register their interest by invoking a method in the Thread or Runnable class to add themselves to the list. If instances of more than one class are interested in the result, then a new interface can be defined that all these classes implement. The interface would declare the callback methods. This is exactly how events are handled in the AWT and JavaBeans.