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.
Implementing Runnable
Suppose we wanted to make an
implementation of our Solver
interface (from
Example 3.1) that was runnable within a thread. We
may want to wrap the solver with a multithreaded server so that
multiple clients can submit ProblemSet
s. 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.
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 anInputStream
and anOutputStream
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 ofSolve()
first attempts to read the problem to be solved from its input stream. If successful, it calls the overriddenSolve()
method with theProblemSet
as the argument. TheSolve(ProblemSet)
implementation solves the problem, then writes the results to the solver’s output stream.- Implementation of run() method from Runnable
The
RunnableSolver
’srun()
method simply callsSolve()
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. 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.
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.
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 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.
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
.
Get Java Distributed Computing now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.