4.2. Making a Thread
The choice between extending the Thread class or
implementing the Runnable interface with your
application objects is sometimes not an obvious one. It's also
usually not very important. Essentially, the difference between the
two classes is that a Thread is supposed to
represent how a thread of control runs (its
priority level, the name for the thread), and a
Runnable defines what a
thread runs. In both cases, defining a subclass usually involves
implementing the run() method to do whatever
work you want done in the separate thread of control.
Most of the time we want to specify what runs in a thread, so in most
cases you may want to implement the Runnable
interface. With a Runnable subclass, you can use
the same object with different types of Thread
subclasses, depending on the application. You might use your
implementation of Runnable inside a standard
Thread in one case, and in another you might run
it in a subclass of Thread that sends a notice
across the network when it's started.
On the other hand, directly extending Thread can
make your classes slightly easier to use. You just create one of your
Thread subclasses and run it, instead of
creating a Runnable subclass, putting into
another Thread, and running it. Also, if your
application objects are subclasses of Thread,
then you can access them directly by asking the system for the
current thread, or the threads in the current thread group, etc. Then
you can cast the object to its subclass and call specialized methods
on it, maybe to ask it how far it's gotten on whatever task you
gave it.
In the next sections we'll look at how to both implement
Runnable and extend Thread
to make an object that executes in an independent thread. We'll
return to our Solver example, making it usable
in a multithreaded agent within a distributed system. The examples in
this section will use fairly basic network communications, based on
sockets and I/O streams, but the concepts extend pretty easily to
distributed object scenarios.
4.2.1. Implementing Runnable
Suppose we wanted to make an
implementation of our Solver interface (from
Example 4-1) that was runnable within a thread. We
may want to wrap the solver with a multithreaded server so that
multiple clients can submit ProblemSets. In this
case, there isn't really a compelling reason to extend the
Thread class with the functionality of our
Solver, since we don't have any special
requirements on how the thread is run. So we would probably choose to
implement the Runnable interface with the
RunnableSolver class shown in Example 4-1.
Example 4-1. A Solver Runnable Within a Thread
package dcj.examples;
import java.lang.Runnable;
import java.io.*;
//
// RunnableSolver - An implementation of Solver that can be used as the
// the body of a Thread.
//
public class RunnableSolver implements Runnable, Solver {
// Protected implementation variables
protected ProblemSet currProblem = null;
protected OutputStream clientOut = null; // Destination for solutions
protected InputStream clientIn = null; // Source of problems
// Constructors
public RunnableSolver(InputStream cin, OutputStream cout) {
super();
clientIn = cin;
clientOut = cout;
}
public boolean Solve() {
boolean success = true;
SimpleCmdInputStream sin = new SimpleCmdInputStream(clientIn);
String inStr = null;
try {
System.out.println("Reading from client...");
inStr = sin.readString();
}
catch (IOException e) {
System.out.println("Error reading data from client.");
return false;
}
if (inStr.compareTo("problem") == 0) {
try {
inStr = sin.readString();
}
catch (IOException e) {
System.out.println("Error reading data from client.");
return false;
}
System.out.println("Got \"" + inStr + "\" from client.");
double problem = Double.valueOf(inStr).doubleValue();
ProblemSet p = new ProblemSet();
p.Value(problem);
success = Solve(p);
}
else {
System.out.println("Error reading problem from client.");
return false;
}
return success;
}
public boolean Solve(ProblemSet s) {
boolean success = true;
if (s == null) {
System.out.println("No problem to solve.");
return false;
}
System.out.println("Problem value = " + s.Value());
// Solve problem here...
try {
s.Solution(Math.sqrt(s.Value()));
}
catch (ArithmeticException e) {
System.out.println("Badly-formed problem.");
success = false;
}
System.out.println("Problem solution = " + s.Solution());
System.out.println("Sending solution to output...");
// Write the solution to the designated output destination
try {
DataOutputStream dout = new DataOutputStream(clientOut);
dout.writeChars("solution=" + s.Solution() + "\n");
}
catch (IOException e) {
System.out.println("Error writing results to output.");
success = false;
}
return success;
}
public boolean Problem(ProblemSet s) {
currProblem = s;
return true;
}
public boolean Iterations(int dummy) {
// Not used on this solver
return false;
}
public void PrintResults(OutputStream os) {
PrintStream pos = new PrintStream(os);
pos.println("Problem solution: " + currProblem.Solution());
}
public void run() {
Solve();
}
}
Here are the critical features to note about the
RunnableSolver in Example 4-1:
-
Constructor with input/output stream arguments
-
The
constructor defined for RunnableSolver takes an
InputStream and an
OutputStream as arguments. These will be used by
the solver to read the problem to be solved and to write out the
results of the solver. The input/output streams could be attached to
an active agent/client over a socket or pipe, or they might be
connected to static data source/destinations like files, databases,
etc.
-
Implementations of Solve() methods from Solver interface
-
The RunnableSolver implementation of
Solve() first attempts to read the problem to be
solved from its input stream. If successful, it calls the overridden
Solve() method with the
ProblemSet as the argument. The
Solve(ProblemSet) implementation solves the
problem, then writes the results to the solver's output stream.
-
Implementation of run() method from Runnable
-
The RunnableSolver's
run() method simply calls
Solve() to solve the current problem.
All together, the RunnableSolver class provides
a Solver that can be created with connections to
just about any kind of "client," and then wrapped with a
Thread and run. The run()
method calls Solve(), which reads the problem
from the client, solves it, and writes the result to the
client.
To demonstrate its use in action, Example 4-2 shows
a RunnableSolveServer class that extends our
SimpleServer class from Chapter 1, "Introduction". The RunnableSolverServer
accepts connections from remote clients, and assigns a
RunnableSolver to solve each client's
problem. It creates a solver with the input and output streams from
the socket connection to the client, then wraps the solver in a
thread and starts the thread.
Example 4-2. A Server for the Runnable Solver
package dcj.examples;
import java.io.*;
import java.net.*;
class RunnableSolverServer extends SimpleServer {
public RunnableSolverServer() { super(3000); }
public RunnableSolverServer(int port) { super(port); }
public static void main(String argv[]) {
int port = 3000;
if (argv.length > 0) {
try {
port = Integer.parseInt(argv[0]);
}
catch (NumberFormatException e) {
System.err.println("Bad port number given.");
System.err.println(" Using default port.");
}
}
RunnableSolverServer server = new RunnableSolverServer(port);
System.out.println("RunnableSolverServer running on port " + port);
server.run();
}
// Override SimpleServer's serviceClient() method to spawn Solver threads
// on each client connection.
public void serviceClient(Socket clientConn) {
InputStream inStream = null;
OutputStream outStream = null;
try {
inStream = clientConn.getInputStream();
outStream = clientConn.getOutputStream();
}
catch (IOException e) {
System.out.println(
"RunnableSolverServer: Error getting I/O streams.");
System.exit(1);
}
RunnableSolver s = new RunnableSolver(inStream, outStream);
Thread t = new Thread(s);
t.start();
}
}
Example 4-3 shows
RunnableSolverClient, a sample client to the
RunnableSolverServer. It simply makes a socket
connection to the RunnableSolverServer's
host and port, writes the problem to the socket's output
stream, and waits for the answer on the input stream.
Example 4-3. A Client for the Runnable Solver
package dcj.examples;
import java.lang.*;
import java.net.*;
import java.io.*;
public class RunnableSolverClient extends SimpleClient {
ProblemSet problem;
public RunnableSolverClient(String host, int port, double pval) {
super(host, port);
problem = new ProblemSet();
problem.Value(pval);
}
public static void main(String argv[]) {
if (argv.length < 3) {
System.out.println(
"Usage: java RunnableSolverClient [host] [port] [problem]");
System.exit(1);
}
String host = argv[0];
int port = 3000;
double pval = 0;
try {
port = Integer.parseInt(argv[1]);
pval = Double.valueOf(argv[2]).doubleValue();
}
catch (NumberFormatException e) {
System.err.println("Bad port number or problem value given.");
}
RunnableSolverClient client =
new RunnableSolverClient(host, port, pval);
System.out.println("Attaching client to " + host + ":" + port + "...");
client.run();
}
public void run() {
try {
OutputStreamWriter wout =
new OutputStreamWriter(serverConn.getOutputStream());
BufferedReader rin = new BufferedReader(
new InputStreamReader(serverConn.getInputStream()));
// Send a problem...
wout.write("problem " + problem.Value() + " ");
// ...and read the solution
String result = rin.readLine();
}
catch (IOException e) {
System.out.println("RunnableSolverClient: " + e);
System.exit(1);
}
}
}
We've reused some classes from Chapter 1, "Introduction" to
implement our RunnableSolverServer and
RunnableSolverClient. The
RunnableSolverServer is an extension of our
SimpleServer, which simply overrides the
serviceClient() method to attach a
RunnableSolver to the client socket. The
RunnableSolverClient is an extension of the
SimpleClient. This allows us to use the
constructor of SimpleClient to establish the
socket connection to the server. All we need to do is provide a new
implementation of the main() method that accepts
an additional argument (the problem to be solved), and override the
run() method from
SimpleClient to do the required communication
with the server.
4.2.2. Extending Thread
Making a Solver
subclass that extends Thread requires just a few
minor changes to our Runnable version. The same
run() method can be used on our
Thread subclass as on the
RunnableSolver, but in this case it's
overriding the run() from
Thread rather than from
Runnable.
To make our multithreaded server work with the
Thread-derived Solver, we
only have to change its serviceClient()
implementation slightly. Rather than creating a
RunnableSolver and wrapping a thread around it,
a Thread-derived Solver
acts as both the Solver and the thread, so we
only need to create one for the incoming client, then
start() it:
ThreadSolver ts = new ThreadSolver(inStream, outStream);
ts.start();
Our client will work with the Thread-derived
Solver without changes. It just wants to connect
to a Solver over a socket--it doesn't
care if the Solver is running as a Thread, or
running inside another Thread.
 |  |  |
| 4.1. Thread and Runnable |  | 4.3. Managing Threads at Runtime |

Copyright © 2001 O'Reilly & Associates. All rights reserved.
|
|