Threads

4 April 1996

Java was designed to be a language for writing network distributed applications. When programs must communicate, even over the fastest networks, they spend much of the time waiting for new data to arrive. To operate efficiently, and provide the best service, programs have to be able to use the idle time productively.

Simple systems such as DOS allow one program to do one thing at a time. DOS programs can manage several devices only by constantly checking each one for new data. A simple terminal program had to loop between checking for keyboard input (to send out the modem) and modem input (to write to the screen). Add a network or a database and the program becomes unreasonably complex.

Better systems, like Windows 95, Windows NT, OS/2, or Unix, allow the program to create multiple threads of execution. Each thread runs a routine written to perform one simple function. The terminal program would be rewritten with a thread to read from the keyboard, and a second to read from the modem.

Among computer wizards, the term "thread" refers to the simplest and least expensive version of this idea. The terms "task" and "process" include a new thread, but they may also load a new program, create a new virtual memory, or partition ownership of files and network sessions.

The first thread is not an option. It is created by the system to run the main routine of the program. The thread continues until the program ends, ether because the main routine returned or because an error occurred and was not handled. Secondary threads behave much like the first thread, except that they don't run "main". In Java, they run "run".

C Thread Run - Run Thread run()

The simplest way to program a thread is to define a new class that extends the standard Java Thread class. This new derived class must have a non-static public method named "run" that takes no arguments. To start a new thread of execution, dynamically allocate (with new) an object of the Class and call its start() method (which was inherited from Thread).

class Background extends Thread{
… /* define instance variables */
public run() { /*do some stuff */…}
}

Background btask = new Background;
btask.start();

If the program replaced the call to start with a direct call to run, btask.run(), then the run method would have started executing under the original thread. It would be associated with the object designed by the btask reference variable. When the run method returned, the calling program would continue execution with the next statement.

By calling btask.start(), a new thread is created. As before, the run() method of the Background class executes and is associated with the object that btask designated when start() was called. However, the caller of start() does not wait for it to return. It continues execution with the next statement, and now both threads run in parallel.

As long as the new thread executes, it has an implied reference to the object used to start it. The object will not be subject to garbage collection even if the main thread doesn't save the reference value in a variable. Some programming examples use this feature to save an extra line of coding:

(new Background).start();

There is an alternate syntax for starting a thread which is a bit more complicated. The programmer can generate some other class that does not extend Thread but which does have a "run" method. To demonstrate that it has met this requirement, the class declares that it implements Runnable, an interface with one method named run(). Then the name of the class is passed as an argument to the constructor for the Thread class:

class Background2 extends Something implements Runnable {
… /* define instance variables and methods */
public run() { /*do some stuff */…}
}

Background2 btask = new Background2;
Thread t = new Thread(btask);
t.start();

Generally the first method (extending Thread) is easiest to code and remember. The Runnable approach requires more study before it becomes natural. However, most threads are created to do one thing. The task for which the thread was created is often associated with an object of some standard Java or user-defined class (a network socket, a disk file data stream, a database query). Therefore, the Runnable technique more compact because the object used to start the thread can be the object that represents the session, file, or transaction on which the thread must operate.

Multiple Instances

In some programs, threads are simply used to divide an assembly line process up into different units that can each proceed at its own pace. There would be one thread for each phase of processing.

However, Java is a language for network distributed applications. It is common for network servers (and even some clients) to establish a different thread to handle each network connection to another node.

Network communication is handled by the java.net package. There are high-level connections represented by a URL, but the more basic connection is an Internet session represented by a Socket object. A client creates a Socket for each connection to a server, and the server creates one for each client.

The Socket class provides a method that generates an InputStream object (a file from which a Java program can read data) and an OutputStream object (a file for writing). Although the Socket object represents the data, the derived stream objects are used to send and receive data.

In most network protocols, the client sends a request block of data and the server sends a response. This simple exchange requires only one thread per Socket. The thread first reads the request, then it performs the operation, then it generates the response, and then it goes back and reads a new request.

In a few cases, the client and server may generate data at any time and send it to each other. This requires the program to be reading data all the time, and occasionally sending it. This requires two threads per Socket, one for the InputStream and one for the OutputStream.

In all these cases, the Java thread is closely associated with another object (a Socket, InputStream, or OutputStream). Once the problem has been properly analyzed, it should be relatively easy to identify the thread classes required and the other objects, that generate data or signal events, to which each thread is associated.

Continue Back PCLT

Copyright 1996 PC Lube and Tune -- Java H. Gilbert