Java Network Programming (3rd ed) [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Java Network Programming (3rd ed) [Electronic resources] - نسخه متنی

Harold, Elliotte Rusty

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








5.2 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 information 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. For example, suppose that instead of simply printing out
the SHA digest, as in Example 5-1 and Example 5-2, the digest thread needs to return the digest
to the main thread of execution. Most people's first
reaction is to store the result in a field and provide a getter
method, as shown in Example 5-3 and Example 5-4. Example 5-3 is a
Thread subclass that calculates a digest for a
specified file. Example 5-4 is a simple command-line
user interface that receives filenames and spawns threads to
calculate digests for them.


Example 5-3. A thread that uses an accessor method to return the result


import java.io.*;
import java.security.*;
public class ReturnDigest extends Thread {
private File input;
private byte[] digest;
public ReturnDigest(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( );
digest = sha.digest( );
}
catch (IOException ex) {
System.err.println(ex);
}
catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
public byte[] getDigest( ) {
return digest;
}
}


Example 5-4. A main program that uses the accessor method to get the output of the thread


import java.io.*;
public class ReturnDigestUserInterface {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
// Calculate the digest
File f = new File(args[i]);
ReturnDigest dr = new ReturnDigest(f);
dr.start( );
// Now print the result
StringBuffer result = new StringBuffer(f.toString( ));
result.append(": ");
byte[] digest = dr.getDigest( );
for (int j = 0; j < digest.length; j++) {
result.append(digest[j] + " ");
}
System.out.println(result);
}
}
}

The ReturnDigest class stores the result of the
calculation in the private field digest, which is accessed via
getDigest( ). The main( )
method in ReturnDigestUserInterface loops through
a list of files from the command line. It starts a new
ReturnDigest thread for each file and then tries
to retrieve the result using getDigest( ).
However, when you run this program, the result may not be what you
expect:

D:\JAVA\JNP3\examples\05>java ReturnDigestUserInterface *.java
Exception in thread "main" java.lang.NullPointerException
at ReturnDigestUserInterface.main(ReturnDigestUserInterface.java,
Compiled Code)

The problem 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,
dr.getDigest( ) returns null,
and the first attempt to access digest throws a
NullPointerException.


5.2.1 Race Conditions


One possibility is to move the call
to dr.getDigest( ) later in the main() method, like this:

public static void main(String[] args) {
ReturnDigest[] digests = new ReturnDigest[args.length];
for (int i = 0; i < args.length; i++) {
// Calculate the digest
File f = new File(args[i]);
digests[i] = new ReturnDigest(f);
digests[i].start( );
}
for (int i = 0; i < args.length; i++) {
// Now print the result
StringBuffer result = new StringBuffer(args[i]);
result.append(": ");
byte[] digest = digests[i].getDigest( );
for (int j = 0; j < digest.length; j++) {
result.append(digest[j] + " ");
}
System.out.println(result);
}
}

If you're lucky, this will work and
you'll get the expected output, like this:

D:\JAVA\JNP3\examples\05>java ReturnDigest2 *.java
BadDigestRunnable.java: 73 -77 -74 111 -75 -14 70 13 -27 -28 32 68 -126
43 -27 55 -119 26 -77 6
BadDigestThread.java: 69 101 80 -94 -98 -113 29 -52 -124 -121 -38 -82 39
-4 8 -38 119 96 -37 -99
DigestRunnable.java: 61 116 -102 -120 97 90 53 37 -14 111 -60 -86 -112
124 -54 111 114 -42 -36 -111
DigestThread.java: 69 101 80 -94 -98 -113 29 -52 -124 -121 -38 -82 39
-4 8 -38 119 96 -37 -99

But let me emphasize that point about being lucky. You may not get
this output. In fact, you may still get a
NullPointerException. 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, we're back where we
started:

D:\JAVA\JNP3\examples\05>java ReturnDigest2 ReturnDigest.java
Exception in thread "main" java.lang.NullPointerException
at ReturnDigest2.main(ReturnDigest2.java, Compiled Code)

Whether you get the correct results or this exception depends on many
factors, including how many threads the program spawns, 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. This is called a race condition.
Getting the correct result depends on the relative speeds of
different threads, and you can't control those! We
need a better way to guarantee that the getDigest() method isn't called until the digest is
ready.


5.2.2 Polling


The solution
most novices adopt is to make the getter method return a flag value
(or perhaps throw an exception) 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. For
example:

public static void main(String[] args) {
ReturnDigest[] digests = new ReturnDigest[args.length];
for (int i = 0; i < args.length; i++) {
// Calculate the digest
File f = new File(args[i]);
digests[i] = new ReturnDigest(f);
digests[i].start( );
}
for (int i = 0; i < args.length; i++) {
while (true) {
// Now print the result
byte[] digest = digests[i].getDigest( );
if (digest != null) {
StringBuffer result = new StringBuffer(args[i]);
result.append(": ");
for (int j = 0; j < digest.length; j++) {
result.append(digest[j] + " ");
}
System.out.println(result);
break;
}
}
}
}

This solution works. It gives the correct answers in the correct
order and it works irrespective of how fast the individual threads
run relative to each other. However, it's doing a
lot more work than it needs to.


5.2.3 Callbacks


In fact,
there's a much simpler, more efficient way to handle
the problem. 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 (like a five-year-old repeatedly asking,
"Are we there yet?" on a long car
trip, and almost as annoying), 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 its creator
back 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 is 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 the
answer. For instance, Example 5-5 shows a
CallbackDigest class that is much the same as
before. However, 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.

Example 5-5. CallbackDigest


import java.io.*;
import java.security.*;
public class CallbackDigest implements Runnable {
private File input;
public CallbackDigest(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( );
CallbackDigestUserInterface.receiveDigest(digest,
input.getName( ));
}
catch (IOException ex) {
System.err.println(ex);
}
catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
}

The
CallbackDigestUserInterface class shown in Example 5-6 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 named on the command line. It does
not attempt to actually read, print out, or in any other way work
with the results of the calculation. Those functions are handled by a
separate method, receiveDigest( ).
receiveDigest( ) 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, receiveDigest( ) runs inside the
digesting threads rather than inside the main thread of execution.

Example 5-6. CallbackDigestUserInterface


import java.io.*;
public class CallbackDigestUserInterface {
public static void receiveDigest(byte[] digest, String name) {
StringBuffer result = new StringBuffer(name);
result.append(": ");
for (int j = 0; j < digest.length; j++) {
result.append(digest[j] + " ");
}
System.out.println(result);
}
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
// Calculate the digest
File f = new File(args[i]);
CallbackDigest cb = new CallbackDigest(f);
Thread t = new Thread(cb);
t.start( );
}
}
}

Example 5-5 and Example 5-6 use
static methods for the callback so that
CallbackDigest only needs to know the name of the
method in CallbackDigestUserInterface to call.
However, it's not much harder (and
it's considerably more common) to call back to an
instance method. In this case, the class making the callback must
have a reference to the object it's calling back.
Generally, this reference is provided as an argument to the
thread's constructor. 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.
For instance, Example 5-7 shows a
CallbackDigest class that is much the same as
before. However, 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 itself is set
in the constructor.


Example 5-7. InstanceCallbackDigest


import java.io.*;
import java.security.*;
public class InstanceCallbackDigest implements Runnable {
private File input;
private InstanceCallbackDigestUserInterface callback;
public InstanceCallbackDigest(File input,
InstanceCallbackDigestUserInterface callback) {
this.input = input;
this.callback = callback;
}
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( );
callback.receiveDigest(digest);
}
catch (IOException ex) {
System.err.println(ex);
}
catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
}

The
CallbackDigestUserInterface class shown in Example 5-8 holds the main( ) method as
well as the receiveDigest( ) method used to handle
an incoming digest. Example 5-8 just prints out the
digest, but a more expansive class could do other things as well,
such as storing the digest in a field, using it to start another
thread, or performing further calculations on it.


Example 5-8. InstanceCallbackDigestUserInterface


import java.io.*;
public class InstanceCallbackDigestUserInterface {
private File input;
private byte[] digest;
public InstanceCallbackDigestUserInterface(File input) {
this.input = input;
}
public void calculateDigest( ) {
InstanceCallbackDigest cb = new InstanceCallbackDigest(input, this);
Thread t = new Thread(cb);
t.start( );
}
void receiveDigest(byte[] digest) {
this.digest = digest;
System.out.println(this);
}
public String toString( ) {
String result = input.getName( ) + ": ";
if (digest != null) {
for (int i = 0; i < digest.length; i++) {
result += digest[i] + " ";
}
}
else {
result += "digest not available";
}
return result;
}
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
// Calculate the digest
File f = new File(args[i]);
InstanceCallbackDigestUserInterface d
= new InstanceCallbackDigestUserInterface(f);
d.calculateDigest( );
}
}
}

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. In practice, this scheme
proves a lot more flexible. However, there is one caveat. Notice the
addition of the calculateDigest( ) method to start
the thread. 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. It's unlikely in this case,
because starting the new thread is the last thing this constructor
does. Nonetheless, it's at least theoretically
possible. Therefore, it's good form to avoid
launching threads from constructors.

The first advantage of the
callback scheme over the polling
scheme is that it doesn't waste so many CPU cycles.
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, a new interface can
be defined that all these classes implement. The
interface would declare the callback methods. If
you're experiencing déjà vu
right now, that's probably because you have seen
this scheme before. This is exactly how events
are handled in Swing, the AWT, and JavaBeans. The AWT runs in a
separate thread from the rest of the program; components and beans
inform you of events by calling back to methods declared in
particular interfaces, such as ActionListener and
PropertyChangeListener. Your listener objects
register their interests in events fired by particular components
using methods in the Component class, such as
addActionListener( ) and
addPropertyChangeListener( ). Inside the
component, the registered listeners are stored in a linked list built
out of java.awt.AWTEventMulticaster objects.
It's easy to duplicate this pattern in your own
classes. Example 5-9 shows one very simple possible
interface class called DigestListener that
declares the digestCalculated( ) method.


Example 5-9. DigestListener interface


public interface DigestListener {
public void digestCalculated(byte[] digest);
}

Example 5-10 shows the Runnable
class that calculates the digest. Several new methods and fields are
added for registering and deregistering listeners. For convenience
and simplicity, a java.util.Vector manages the
list. The run( ) method no longer directly calls
back the object that created it. Instead, it communicates with the
private sendDigest( ) method, which sends the
digest to all registered listeners. The run( )
method neither knows nor cares who's listening to
it. This class no longer knows anything about the user interface
class. It has been completely decoupled from the classes that may
invoke it. This is one of the strengths of this approach.


Example 5-10. The ListCallbackDigest class


import java.io.*;
import java.security.*;
import java.util.*;
public class ListCallbackDigest implements Runnable {
private File input;
List listenerList = new Vector( );
public ListCallbackDigest(File input) {
this.input = input;
}
public synchronized void addDigestListener(DigestListener l) {
listenerList.add(l);
}
public synchronized void removeDigestListener(DigestListener l) {
listenerList.remove(l);
}
private synchronized void sendDigest(byte[] digest) {
ListIterator iterator = listenerList.listIterator( );
while (iterator.hasNext( )) {
DigestListener dl = (DigestListener) iterator.next( );
dl.digestCalculated(digest);
}
}
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( );
this.sendDigest(digest);
}
catch (IOException ex) {
System.err.println(ex);
}
catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
}

Finally, Example 5-11 is a main program that
implements the DigestListener interface and
exercises the ListCallbackDigest class by
calculating digests for all the files named on the command line.
However, this is no longer the only possible main program. There are
now many more possible ways the digest thread could be used.


Example 5-11. ListCallbackDigestUserInterface interface


import java.io.*;
public class ListCallbackDigestUserInterface implements DigestListener {
private File input;
private byte[] digest;
public ListCallbackDigestUserInterface(File input) {
this.input = input;
}
public void calculateDigest( ) {
ListCallbackDigest cb = new ListCallbackDigest(input);
cb.addDigestListener(this);
Thread t = new Thread(cb);
t.start( );
}
public void digestCalculated(byte[] digest) {
this.digest = digest;
System.out.println(this);
}
public String toString( ) {
String result = input.getName( ) + ": ";
if (digest != null) {
for (int i = 0; i < digest.length; i++) {
result += digest[i] + " ";
}
}
else {
result += "digest not available";
}
return result;
}
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
// Calculate the digest
File f = new File(args[i]);
ListCallbackDigestUserInterface d
= new ListCallbackDigestUserInterface(f);
d.calculateDigest( );
}
}
}


/ 164