The java.net.InetAddress class is Java's encapsulation of an IP addresses. It is used by most of the other networking classes, including Socket, ServerSocket, URL, DatagramSocket, DatagramPacket, and more.
A thread with a little t is a separate independent path of execution in the virtual machine. There is one-to-one relationship between threads executing in the virtual machine and Thread objects constructed by the virtual machine. 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. The second one has an advantage that it more cleanly separates the task that the thread performs from the thread itself.
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 e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
String root = "/home/captain/Downloads/";
File f2 = new File(root);
String[] dir;
dir = f2.list();
for(int i = 0 ;i < dir.length;i++){
File f = new File(root+dir[i]);
if(f.isFile()){
Thread t = new DigestThread(f);
t.start();
}
}
}
}
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.
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 e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
String root = "/home/captain/Downloads/";
File f2 = new File(root);
String[] dir;
dir = f2.list();
for (int i = 0; i < dir.length; i++) {
File f = new File(root + dir[i]);
if (f.isFile()) {
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.
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.
DigestInputStream din = new DigestInputStream(in, sha);
int b;[#M_더보기|접기|
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#]
package com.oldroid.threads;
import java.io.File;
public class ReturnDigestUserInterface {
public static void main(String[] args) {
String root = "/home/captain/Downloads/";
File f2 = new File(root);
String[] dir;
dir = f2.list();
// Calculate the digest
for (int i = 0; i < dir.length; i++) {
// Calculate the digest
File f = new File(root + d[#M_더보기|접기|
ir[i]);
if (f.isFile()) {
ReturnDigest dr = new ReturnDigest(f);
dr.start();
// 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);
}
}
}
_M#]
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
public static void main(String[] args) {
String root = "/home/cap
tain/Downloads/";
File f2 = new File(root);
String[] dir;
dir = f2.list();
ReturnDigest[] digests = new ReturnDigest[f2.list().length];
// Calculate the digest
for (int i = 0; i < dir.length; i++) {
File f = new File(root +
dir[i]);
if (f.isFile()) {
digests[i] = new ReturnDigest(f);
digests[i].start();
}
}
for (int i = 0; i < dir.length; i++) {
File f = new File(root + dir[i]);
// Now print the result
byte[] digest = digests[i].getDigest();
if (digest != null) {
StringBuffer result = new StringBuffer(f.toString());
result.append(": ");
for (int j = 0; j < digest.length; j++) {
result.append(digest[j] + " ");
}
System.out.println(result);
break;
}
}
}
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.
public static void main(String[] args) {
String root = "/home/captain/Downloads/";
File f2 = new File(root);
String[] dir;
dir = f2.list();
ReturnDigest[] digests = new ReturnDigest[f2.list().length];
// Calculate the digest
for (int i = 0; i < dir.length; i++) {
File f = new File(root + dir[i]);
if (f.isFile()) {
digests[i] = new ReturnDigest(f);
digests[i].start();
}
}
for (int i = 0; i < dir.length; i++) {
File f = new File(root + dir[i]);
// Now print the result
byte[] digest = digests[i].getDigest();
StringBuffer result = new StringBuffer(f.toString());
result.append(": ");
for (int j = 0; j < digest.length; j++) {
result.append(digest[j] + " ");
}
System.out.println(result);
}
}
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.
package com.oldroid.threads;
import java.io.File;
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){
String root = "/home/captain/Downloads/";
File f2 = new File(root);
String[] dir;
dir = f2.list();
for(int i = 0 ;i < dir.length;i++){
File f = new File(root+dir[i]);
if(f.isFile()){
CallbackDigest cb = new CallbackDigest(f);
Thread t = new Thread(cb);
t.start();
}
}
}
}
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.
package com.oldroid.threads;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class InstanceCallbackDigest implements Runnable {
DigestInputStream din = new DigestInputStream(in, sha);
while(din.read()!= -1);
din.close();
byte[] digest = sha.digest();
callback.receiveDigest(digest);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
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.
package com.oldroid.threads;
import java.io.File;
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();
}
public 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){
String root = "/home/captain/Downloads/";
File file = new File(root);
String[] dir = file.list();
for(int i = 0 ;i < dir.length;i++){
File f = new File(root+dir[i]);
if(f.isFile()){
InstanceCallbackDigestUserInterface d = new InstanceCallbackDigestUserInterface(f);
d.calculateDigest();
}
}
}
}
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.
The Writer class mirrors the java.io.OutputStream class. It's abstract and has two protected constructors.
protected Writer()
protected Writer(Object lock)
public abstract void write(char[] text, int offset, int length) throws IOException
public void write(int c) throws IOException
public void write(char[] text) throws IOException
public void write(String s) throws IOException
public void write(String s, int offset, int length) throws IOException
public abstract void flush() throws IOException
public abstract void close() throws IOException
The write(char[] text, int offset, int length) method is the base method in temrs of which the other four write() methods are implemented. A subclass must override at least this method as well as flush() and close(). LIke OutputStream, the Writer class is never used directly, only polymorphically through one of its subclasses. Writers may be buffered, either directly by being chained to a BufferedWriter or indirectly because their underlying output stream is buffered. To force a write to be committed to the output medium, invoke the flush() method. Once a writer has been closed, further writes will throw IOExceptions.
OutputStreamWriter
OutputStreamWriter is the most important concrete subclass of Writer. An OutputSTreamWriter receives Unicode characters from a Java program. It converts these into bytes according to a specified encoding and writes them onto an underlying output stream. If no encoding is specified, the default encoding for the platform is used. (In the United States, the default encoding is ISO Latin-1 on Solaris and Windows, MacRoman on the Mac.)
Readers
The Reader class mirrors the java.io.InputStream class.
protected Reader()
protected Reader(Object lock)
public abstract int read(char[] text, int offset, int length) throws IOException
public int read() throws IOException
public int read(char[] text) throws IOException
public long skip(long n) throws IOException
public boolean ready()
public boolean markSupported()
public void mark(int readAheadLimit) throws IOException
public void reset() throws IOException
public abstract void close() throws IOException
Like InputStream and Writer, the Reader class is never used directly, only polymorphically through one of its subclasses. The read() method returns a single Unicode character as an int with a value from 0 to 65,535 or -1 on end of stream. The skip(long n) method skips n characters. The mark() and reset() methods allow some readers to reset back to a marked position in the character sequence. The markSupported() method tells you whether this reader supports marking and resetting. The close() method closes the reader and any underlying input stream so that further attempts to read from it will throw IOException.
InputStreamReader is the most important concrete subclass of Reader. An InputStreamReader reads bytes from an underlying input stream such as a FileInputStream or TelnetInputStream. It converts these into characters according to a specified encoding and returns them.
Buffered readers and writers
The BufferedReader and BufferedWriter classes are the character-based equivalents of the byte-oriented BufferedInputStream and BufferedOutputStream classes. Where BufferedInputStream and BufferedOutputStream use an internal array of bytes as a buffer, BufferedReader and BufferedWriter use an internal array of chars.
When a program reads from a BufferedReader, text is taken from the buffer rather than directly from the underlying input stream or other text source. When the buffer empties, it is filled again with as much text as possible, even if not all of it is immediately needed. This will make future reads much faster.
When a program writes to a BufferedWriter, the text is placed in the buffer. The text is moved to the underlying output stream or other target only when the buffer fills up or when the writer is explicitly flushed. This can make writes much faster than would otherwise be the case.
public BufferedReader(Reader in, int buffersize)
public BufferedReader(Reader in)
public BufferedWriter(Writer out)
public BufferedWriter(Writer out, int buffersize)
Both BufferedReader and BufferedWriter have the usual methods associated with readers and writers, like read(), ready(), write(), and close(). They each have two constructors used to chain the BufferedReader or BufferedWriter to an underlying reader or writer and to set the size of the buffer. If the size is not set, then the default size of 8,192 characters is used.
PrintWriter
The PrintWriter class is a replacement for Java 1.0's PrintStream class that properly handles multibyte character sets and international text. Most of these methods behave the same for PrintWriter as they do for PrintStream. The exceptions are that the four write() methods write characters rather than bytes and that if the underlying writer properly handles character set conversion, then so do all the methods of the PrintWriter. This is an improvement over the noninternationalizable PrintStream class, but it's still not good enough for network programming. PrintWriter still has the problems of platform dependency and minimal error reporting that plague PrintStream.