Software System Components 2 - Year 2, Semester 2, Week 7
Concurrency - Dr Steve Vickers
1: Threads
A Java program always consists of several tasks being done at the same
time, or concurrently. This
is achieved by a very clever use of the processor chip. Although it can
only execute code for a single task at any given time, the code being
executed is sometimes for one task, sometimes for another, and
sometimes for the operating system that coordinates them. They are
carefully interleaved to give the illusion of several pieces of code
being executed at once.
Concurrency sets some of the most dangerous traps in all of
programming. Concurrent tasks can cooperate successfully thousands of
times, and then just once in a while interfere in such a way that
neither quite does what its code says it should. We shall see much more
this next week. These problems are a nightmare to debug. It is hard to
relate the fault to what the code says, and because it is sporadic, it
is also very hard to reproduce. In critical software such faults can
cause lost money or even lost lives. The good news is that there are
safe programming techniques for eliminating them. You cannot consider
yourself a competent software engineer unless you are aware of the
concurrency dangers and know how to avoid them.
In the Java system the concurrent tasks are called threads. There will be different
threads present anyway, but you can also create your own.
At the end of this week's lectures you should know -
- the role of the main
and event dispatch threads;
- how to create threads;
- how to start and stop threads and how to send them to sleep for a
fixed time;
- how to use thread pools.
Some threads in action
I use examples from the turtles package. If you did not do the year 1
module "Software Workshop 1", you can find instructions for installing
and using the turtles at the end of these
notes.
Example: OffCircles
In this example (file OffCircles.java),
the main method repeatedly draws circles, but with a randomness in the
side length that distorts their shape. Thus you see lots of
"off-circles".
public
static void main(String[] args) {
Turtle theTurtle = new Turtle();
new TurtleGUI(theTurtle);
while (true){
theTurtle.move(Math.random()*5);
theTurtle.turn(1);
}
}
The point to note is that while it is drawing off-circles, you can also
control the turtle with its buttons.
Example: OffCirclesGo
This uses files OffCirclesGo.java
and GoFrame.java. It is very similar to
OffCircles,
except that the off-circles
are drawn by a listener on a button. The intention is that you press
the button to start drawing the off-circles.
Why does it not work? How might you try to debug it?
- If you replace "while (true)"
by a for-loop with 1000 iterations,
it successfully draws about 3 off-circles every time you press the Go
button. So we know the button can
work.
- Some information can be gained from the debugger, though it does
not work very well with graphics.
- I'm going to put "assert" statements in to
force it to stop with an AssertionError.
What we see will also explain something about exception.
Example
Take OffCircles.java, and put "assert
false;" just before the
while loop. When you run it, it shows an exception report
Exception
in thread "main" java.lang.AssertionError
This is roughly as expected, except that the turtle GUI is still working.
You can use the buttons to control the turtle, although there are no
off-circles. How can that be? Doesn't the exception stop everything?
Example
Do the same with OffCirclesGo.java. This time the exception
report is
Exception
in thread "AWT-EventQueue-0" java.lang.AssertionError
Two different threads
The exception reports tell us about two different threads, "main" and
"AWT-EventQueue-0".
A thread is a single flow of control through a program. If you follow
through the statements that are executed, into methods as they are
called and then back out of them on return, then you are following a
thread.
When you run a Java program, a thread is created to start executing the
main method and all the methods called by it. Naturally enough, this is
the "main" thread.
But a Java program can have more than one thread - it can be multithreaded. The threads then run
"concurrently". This means they look as though they are running at the
same time as each other, though more usually the Java system is
cleverly interleaving them, running a little bit of one, then a little
bit of another, and so on.
Some of the threads are set up automatically by the system to perform
special tasks, while others might be set up by explicit code in your
program. (We shall soon see how to do that.)
Amongst the automatic system threads one of the most important is
the event dispatch thread,
"AWT-EventQueue-0". This has the job of running all GUI code including
the paint methods for
screen components, and the actionPerformed
methods for buttons and so on.
We now have the answer to the off-circles question. In OffCircles, it is the main
thread that executes the infinite loop of move and turn methods to draw the
off-circles. Concurrently, the event dispatch thread paints the turtle
and also
looks after the buttons, executing the move and turn methods for them. In
OffCirclesGo the event
dispatch thread executes the actionPerformed
method in response to the Go button, and so goes into the infinite
loop. After that, the event dispatch thread never gets a chance to execute any paints
or any other actionPerformed methods. So although the turtle is
in fact doing all the off-circles, nothing is seen on the screen, and
the buttons don't respond. The infinite loop has "hijacked" the event
dispatch thread.
In fact this is one very common use for setting up your own threads -
so as not to hijack the event dispatch thread. A good rule is never to
let the event dispatch thread do anything that might take a long time.
Setting up your own threads
To set up a thread you normally need two different objects. One, the
thread itself, knows how to execute code, and the other, the run-object, knows what code to
execute.
The thread is an instance of Java.lang.Thread.
It has all the features that enable it to take part in the operating
system as a flow of control.
However, you also want to specify the code that it will excute. You can
only write code as part of a method in a class, and the code for a
thread is written as a method called run. You then create the
run-object as an
instance of this class.
The way the thread and the run-object work
together is a bit like the way a button works with an action
listener, and again there is an interface involved. It is java.lang.Runnable:
public
interface Runnable {
public
abstract void run();
}
The general scheme is this.
- Write a class, implementing Runnable,
whose run method is the
code you want to be executed by a thread.
- Create an instance of that class. This is the run-object.
- Create an instance of Thread,
using the run-object as constructor
parameter.
- Call the start()
method on the thread. This starts the thread executing run() on its run-object.
A beginner's mistake: Don't
confuse start() with run(). If you call the run
method on the thread, it will execute the right code but on the wrong thread. It
won't start the new thread, but will continue executing on the old one.
Example: OffCirclesGo corrected
Here (file OffCirclesGoGood.java)
is a correct version of the class OffCircler
for the OffCirclesGo example. An
instance of OffCircler is
the action listener for the Go button. Instead of just executing the
off-circle loop on the event dispatch thread, we create a new thread to
do it. That means the event dispatch thread can go back to painting and
handling button presses.
class
OffCircler implements ActionListener {
private Turtle
theTurtle;
/**
*
Constructor initializing theTurtle from parameter.
* @param
theTurtle value for theturtle field.
*/
public
OffCircler(Turtle theTurtle) {
this.theTurtle = theTurtle;
}
/**
* Draw
off-circles for ever on theTurtle.
* @param
e event parameter from action event (not used)
*/
public void
actionPerformed(ActionEvent e) {
Thread t = new Thread (new OffCirclerRunnable());
t.start();
}
private class
OffCirclerRunnable implements Runnable {
public void run() {
while (true){
theTurtle.move(Math.random()*5);
theTurtle.turn(1);
}
}
}
}
We put the off-circle loop in the run
method for OffCircleRunnable.
In actionPerformed, we
create a new OffCirclerRunnable
instance, and make it the constructor parameter for a new thread t. Then we start t.
Inner classes
We made the class OffCirclerRunnable private, and defined it inside the class OffCircler. It is called an inner class in OffCircler. Runnable classes
don't have to be defined like that, but in this case it has an
interesting advantage. Its methods have access to the field theTurtle in OffCircler. (Technically, every
instance of OffCirclerRunnable
is attached to an instance of OffCircler
and has access to its fields.)
Unassessed exercise
Find a simpler solution in which the action listener is also the
run-object. For this you won't need a separate class OffCirclerRunnable. Instead, OffCircler will have to
implement Runnable.
Thread states
There are various states a thread may be in.
- After the thread has been created, but before start has been called on it,
the thread is new.
- Between the call of start
and termination of run,
it is alive.
- When run has
terminated, the thread is terminated
(or dead).
You can tell whether a thread is alive or not by calling the isAlive method on it.
- An alive thread that is able to execute its run method is runnable. (Don't confuse this with
the Runnable object that
provides the thread's run method.)
There may be many runnable threads, but normally only one at a time can
be actually running - for the simple reason that the computer may have
only one processor chip.
However, there are also various reasons why an alive thread might not
be runnable. We shall see those later. It might have put itself to
sleep for a fixed time, or it might be waiting for something to be done
by other threads. Such a thread is suspended.
Thread pools
Threads consume a lot of system resources. Each thread uses a lot of
memory, and creating and destroying threads takes a lot of time. This
can be a big overhead if you have lots of small tasks that
need to be run on separate threads. Therefore it is desirable to be
able to limit the total number of threads, and to maximize the use of
each thread once it has been created.
"Thread pools" are a way to do this. A thread pool keeps a fixed set of
threads that are
available for running a succession of short tasks, each presented as a
run- object (i.e. an instance of a class that implements Runnable). Think of the
threads in the pool as being like counter staff in a post office, and
the run-objects as
being like customers, each with its own task to perform. The threads
take
on any tasks that come along, and when all the threads are busy the
tasks wait in a queue.
There are various ways to create thread pools and similar gadgets,
using classes in the package java.util.concurrent.
We shall describe one of the simplest.
To create a thread pool with nThreads
threads in it, use this static factory method from java.util.concurrent.Executors.
public static ExecutorService newFixedThreadPool(int nThreads)
Its result is the thread pool. Once you have your thread pool,
there are many methods you can call on it. Here are the most useful.
void execute(Runnable command)
The parameter "command" is
task to be submitted to the pool. If any threads are available to
run it, one of them does so. Otherwise the task waits in a queue until
a thread is free. Throws RejectedExecutionException
if the thread is shut down (see below).
void shutdown()
Shuts the pool down. No new tasks will be accepted, though tasks still
being run, or in the queue, will be completed.
List<Runnable> shutdownNow()
Shuts the pool down, aborts the tasks waiting to be run, and interrupts
those being run. ("Interrupts" will be explained under Stopping a thread. The returned result,
of type List<Runnable>,
is a list of the tasks that were waiting to be run. But often you can
just ignore that and treat shutdownNow
as though it is void.)
Example: CirclesPool
The class CirclesPool draws circles
with a turtle. The main method constructs a thread pool with just one
thread. Then it reads in radii of circles from an input frame, and
submits one task to the pool for each circle. When it reads a negative
radius, it will shut down the pool and stop the main thread. Here is
the central part of the main method.
ExecutorService
pool = Executors.newFixedThreadPool(1);
boolean stopping = false;
while (!stopping) {
double radius
= theInputFrame.getDouble();
if (radius
> 0) {
pool.execute(new Circler(theTurtle, radius));
} else {
pool.shutdown();
stopping = true;
}
}
It uses input frames, which are provided
with the turtle graphics package. Circler
is a class that implements Runnable.
Its run method draws a circle on the given turtle with the given
radius. We shall look at it more closely later.
When you execute CirclesPool,
quickly type in a few radii, for example 10, 20, 30, 40, 50, and then
type in -1. This will create 5 separate Circler run-objects that get
submitted (by execute) to
the thread pool. The single thread in the pool will run them one after
the other. When you enter -1, the pool will shut down. It won't accept
any more tasks, but it will complete all the circles that are already
waiting.
If you replace shutdown
by shutdownNow, the
shutdown behaviour is more drastic. The current circle is finished as
quickly as possible and any other ones still waiting are omitted. We
shall see how this works later.
Stopping a thread
The only safe way to stop a thread is for it to stop itself. This is
because it may be in the middle of complex operations that have to be
cleaned up.
In
every run method you should consider how it can stop.
Call interrupt() on a
thread to signal that it should stop
itself. The effect of this is to "set the interrupt status" of
the thread, something that the run method can detect.
There are two main ways the run
method can check the interrupt status. The simplest is by calling Thread.interrupted(). This
returns a boolean result to say whether or not the interrupt status (of
the currently executing thread) is set. At the same time, it also
resets the interrupt status, so calling Thread.interrupted() a second
time will return false (unless
there has meanwhile been yet another call of interrupt()).
Example: OffCirclesGoStop1
Here (in file OffCirclesGoStop1.java) is
one way to rewrite the run method of OffCirclesGo so that it
stops if the thread is interrupted. The idea is completely simple.
instead of looping forever (while
(true)), we loop until an interrupt
is detected.
public
void run() {
while (!Thread.interrupted()){
theTurtle.move(Math.random()*5);
theTurtle.turn(1);
}
}
Remember! Whenever you write a
run method, consider how
it can detect when the interrupt status is set, and stop. We have now
done that
with our off-circles run.
Now we have rewritten run
so that it can stop, we can use that in other parts of the program.
Here is a version where we can stop the thread by pressing the Go
button a second time. (Hence while the thread is running, the "Go"
button means "stop". Sorry about that. If you are used to pressing
"Start" to make Windows shut down, you should not find this too
confusing.) We rewrite actionPerformed
so that if the thread is running, it gets interrupted. Since actionPerformed has ot remember
what the thread is from one call to the next, we make the thread a
field of OffCircler. We
set this field to null
when there is no thread.
private
Thread theThread = null;
public void
actionPerformed(ActionEvent e) {
if (theThread == null) {
theThread = new Thread (new OffCirclerRunnable());
theThread.start();
} else {
theThread.interrupt();
theThread = null;
}
}
Thread.sleep
The second way to detect the interrupt status looks more complicated,
but is often very convenient.
The static void method Thread.sleep(long
millis) suspends the current thread ("sends it to sleep") for
the given number of milliseconds. (Note:
"long" is the type for
double-length integers. A long
integer is stored in 64 bits, instead of 32 for int.) During that time it is
not runnable, but waiting.
However, if you try to use it on its own in a method, you get a compile
error. This is because sleep throws a checked
exception InterruptedException.
(Remember that "checked"
means you must either put the call of sleep in a try-catch statement,
or declare your own method as throwing the same exception. The only unchecked exceptions are those that
subclass Error or RuntimeException.)
Beginners' error: It is
tempting to squash the exception by catching it but not handling it,
e.g.
try
{
Thread.sleep(1000); //sleep 1 second
} catch (InterruptedException e) {
}
Don't do this with checked
exceptions. The reason they were designed to be checked is that you are
expected
to do something about them when they arise. If you squash them, then -
to quote Joshua Bloch in "Effective Java" - you are not only ignoring
the fire alarm, but also turning it off so nobody else knows it rang.
Why does sleep throw this
exception? It is part of making sure the thread knows if interrupt() is called on it. If
a thread is asleep, it cannot be calling Thread.interrupted() to check
the interrupt status. Therefore -
- If the current thread has its interrupt status set when it tries
to call Thread.sleep(), then it is not allowed to go to sleep - it
stays runnable.
- If a thread is asleep when interrupt() is called on it, then it
is immediately woken up - it becomes runnable again.
In each case, an InterruptedException
is thrown and the interrupt status is reset. (It does not need to be
set any more, because the InterruptedException
means the thread now knows about the interrupt.)
- A thread is interrupted
if some other
thread calls interrupt()
on it.
- Normally, when a thread is interrupted it should try to stop
itself (by finishing its run method).
- There are two ways a thread can find out it has been interrupted:
either by calling interrupted(),
or by getting an InterruptedException.
Dealing with an InterruptedException
in the run method
Remember, you got an InterruptedException
because some other thread wants you to stop and called interrupt() on you. If you know
what to do to stop, then you should catch the exception and arrange the
stop. This generally applies if you call sleep in the run method.
Example: OffCirclesGoStop2
Here is a modification of OffCirclesGoStop1
in which the off-circles
are slowed down with sleeps.
public
void run() {
boolean stopped = false;
while (!stopped){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
stopped =
true;
}
theTurtle.move(Math.random()*5);
theTurtle.turn(1);
}
}
This pattern, with a boolean variable to control stopping, is very
common in properly written run methods, and you should learn it.
Note how we don't need to call interrupted()
any more. Calling sleep
on every repetition allows the method to find out if the interrupt
status is set.
Dealing with an InterruptedException
in other methods
When you call sleep in
methods other than run,
quite often you don't know how to stop the thread - because
you don't know what threads might call the method. In this case you
need to think more carefully.
In the situation where the InterruptException
arises, ask yourself, Can the method
still perform its usual task?
If not, then the method should throw an exception - that is what
exceptions are for. Usually you just declare the method as throwing InterruptedException, and then
you don't need a try-catch round Thread.sleep().
Make sure your Javadoc explains when the exception is thrown.
If the method can
successfully perform its usual task, despite the InterruptedException, then it
is usually more convenient not to rethrow the exception. If you don't, you must make sure the interrupt status is
set again. This is so that when control returns to the run
method it can detect the interrupt. Use the following code.
try
{
Thread.sleep(...);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
The static method Thread.currentThread()
returns a reference to the thread currently executing. This might look
a bit odd - the current thread is interrupting itself. It is really
saying, "I know I must stop, but I don't know what to do about it yet.
I should know when I'm back in the run method. Meanwhile, I must leave
a reminder note to myself."
Example: Circler
We saw CirclesPool before but did not look closely at
its Circler class. Here
is its run method. It uses Thread.sleep.
public
void run() {
for (int i =
0; i < STEPS; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); //no more sleeps
}
theTurtle.move(stepSize);
theTurtle.turn(360.0/STEPS);
}
}
Note that run cannot
throw InterruptedException,
because the Runnable
interface does not declare it to do that. Hence we must use try -
catch. There is a design decision in the way we do it here. When an
interrupt is received, we call interrupt again. The effect is that the
circle still gets drawn but there are no more sleeps. (Each time sleep
is called, it throws InterruptedException.) Hence the circle is
finished off quickly.
There are alternatives to calling interrupt
in the catch-block. This next one would stop immediately without
completing the circle.
}
catch (InterruptedException e) {
return;
}
This one would squash the exception and the circle would be completed
as normal.
}
catch (InterruptedException e) {
}
Squashing an exception is generally a bad idea, for the reasons already
mentioned. But in this case it might be reasonable because we know the
run method is going to terminate anyway.
Example
Suppose you have a graphics component with a method animate() that shows an
animation, drawing a sequence of images over a period of time. It might
use sleep for the timing.
How do you deal with an InterruptedException?
You have to decide whether to abort the animation or complete it.
First alternative: aborts the animation if thread is interrupted. This
is more drastic but quicker.
/**
display animation.
* ...
* throws Interrupted Exception if thread is interrupted
* - and then animation is aborted.
*/
public void animate()
throws InterruptedException
{
...
Thread.sleep(...);
...
}
Second alternative: completes the animation. This is easier but takes
longer to stop the thread.
public
void animate()
{
...
try {
Thread.sleep(...);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
...
}
Deprecated methods and stop()
The Thread class has a
method stop() that forces
the thread to stop executing. So why don't we just use that? The answer
is that stop is deprecated. This means that it is
not recommended, and may (in theory) be withdrawn from future versions
of Java. The reason is for this is that stop is unsafe. While a thread
is updating an object, it may temporarily put
the object in a bad state with its invariants broken. That is normally
OK because the thread is going to repair everything before it finishes.
But if the the thread is stopped in the middle of the update, then the
object is left permanently in the bad state. In fact there is no safe
way to force a thread to
stop. You have to rely on its cooperation. That is why we use interrupt.
Next week
A thread keeps its own return stack and its own values of the actual
parameters and local variables in the methods. However, it does not
keep its own
values of object fields. Those values are stored by the objects, not by
the threads. For example, in OffCircles
two threads were calling methods on one single turtle. That turtle had
to look after the values of its fields, and one of the central problems
is how it can do that when different threads are concurrently accessing
those fields and sharing their values. Next week we shall see just how
bad the problem is and what can be done about it.
Summary
- All Java programs have several threads
of control, including the main thread and the event dispatch thread. You can also
create your own.
- Methods such as paint
and actionPerformed are
run on the event dispatch thread. Make sure you do not hijack it with
code that might take a long time to run. Instead you should set up a
thread of your own to run that code.
- When you create a thread, of class Thread, you should also create
a run-object for it, whose class implements Runnable.
- You start a thread with the method start().
- Do not stop a thread
with the method stop().
It is deprecated.
- Stop a thread with the method interrupt() and write your run method so that it detects
when interrupt has been
called.
- One way to detect an interrupt is by calling interrupted().
- Another is by an InterruptedException,
thrown by sleep (and also
some other methods).
- Deal with InterruptedException
by catching it and finishing run,
or by catching it and calling interrupt,
or by declaring your method throws InterruptedException. Don't
catch it and do nothing unless you are sure that is the right thing to
do.
- A thread can be new, alive, or terminated (or dead). While it is alive, it can be
runnable or suspended. One of the runnable
threads will be running.
- A thread pool uses a
limited number of threads to run a larger number of run objects. The
methods of thread pools are specified by the interface ExecutorService and include execute, shutdown and shutdownNow. Thread pools are
created by factory methods such as Executors.newFixedThreadPool.
Notes
Turtle graphics
If you took the year 1 module "Software Workshop 1" you will know
this already.
The turtle graphics can
be seen as an applet here.
It is in a Java package fyw.turtles.
To use it from netBeans you will need to link the turtles library into
your netBeans project, presumably the project for your Concurrency
(weeks 7-9) exercise.
Linking in the turtles library
For your exercises you will need to use the turtle graphics packages
that we have already written for you. You do not need to look at the
source code or understand it at all. However, you need to add it to the
fywExercises library so that netBeans knows where to find it for your
programs. Proceed as follows.
- Make sure you can see your concurrency project in the Project
pane, with
"Libraries" underneath (after "Source Packages" and "Test Packages").
- Right click on
"Libraries", and select "Add JAR/Folder ...". (JAR stands for Java ARchive, and is
a collection of Java files bundled up into one. But we are adding a
folder.)
- You should get a window "Add JAR/folder". In it you must browse
for a folder called /bham/common/java/packages/fyw/javalib. You are at
the right place when the "File Name" box says "javalib".
- Click "Open".
If you open up "Libraries" under your concurrency project, you should
see two
libraries. One is called "JDK 1.5 (Default)" and is the standard Java
library. The other is called "/bham/common/java/packages/fyw/javalib".
If you open that up, you will see a number of packages including
"fyw.turtles" and "fyw.autoevent". If you see those then you have
successfully linked in the turtles library. You should also be able to
see the source code, if you are interested.
You should also be able to copy the turtles library onto your own
computer.
Using the turtle from Java
The turtle needs two objects created. The turtle itself, without any
GUI, is of class Turtle.
You will also need some GUI to view and control the turtle. This is of
class TurtleGUI, and its
constructor takes as parameter the turtle to be installed in the GUI.
Typically you write
Turtle
theTurtle = new Turtle();
new TurtleGUI(theTurtle);
in your main method. Once
this is done you can control the turtle either from the GUI buttons or
by Java method calls on theTurtle.
Here is a list of the principle methods, instance methods from class Turtle.
public
void move(double distance)
Move turtle ahead through given distance.
public
void turn(double angle)
Turn turtle through given number of degrees (clockwise).
public
void restart()
Clear turtle pane and move turtle to its starting position.
public
void penUp()
"Lift pen up", so that turtle does not leave a line when it moves.
public
void penDown()
"Put pen down", so that turtle does leave a line when it moves.
Input frames
Input frames are a convenient way to input numbers (int or double) into a program. The
class InputFrame is
included in the package fyw.turtles.
An input frame has its own window on the screen, with an editable text
area and a button. When the program needs a number from it, it calls
either getInt() or getDouble(). The user then
types in the number and presses either enter or the button. The input
frame checks the format (if it is invalid the user has to correct it)
and returns the value.
Assert statements
An assert statement takes the form
assert
boolean
expression
;
When the program is run, if the boolean expression evaluates to false
then an AssertionError is
thrown. This is intended as a debugging aid.
Programs should be designed so that their assert statements never do
throw an error. Usual practice is to leave them in the final source
code as comments, but turn off the assertion checking to speed up the
program's execution.
To ensure that assertion checking is turned on, do the following.
- Right click on the project icon.
- Select Properties and click the Run category.
- Ensure that the "VM Options" box (virtual machine options) has
"-ea" typed in it.
This has the effect that when netBeans runs a program, it does it as
though with "java -ea ...".
The -ea option is for "enable assertions".
Anonymous inner classes
Inner classes can be taken a step further, and used "anonymously". Here
is an example equivalent to the above code for actionPerformed.
public
void actionPerformed(ActionEvent e) {
Thread t = new
Thread (new Runnable() {
public void run() {
while (true){
theTurtle.move(Math.random()*5);
theTurtle.turn(1);
}
}
});
t.start();
}
"new runnable()" is
usually quite wrong - you can't instantiate an interface. But here we
are instead creating an new instance of an anonymous class that
implements Runnable by
having the given definition for run.
This use of anonymous inner classes is quite often used for
run-objects, and it also works well for event listeners.
Extending Thread
I described how to set up a thread using two separate objects: one of
class Thread, and a
separate run-object whose class implements Runnable.
There is an alternative way that you often see described (for instance,
in the API). This is to use a single object whose class extends Thread and implements Runnable. This can be more
convenient in a simple one-off program. However, for large pieces of
software the technique with a separate run object is much more
flexible. That is because there are really two independent questions: What code do you want to run? and How do you want to run it? The
run-object looks after the first question, the thread looks after the
second. If you keep them separate then you can vary the two answers
independently. For instance, it is simple to replace a thread by a
thread pool.
I recommend you to use run objects and not extend Thread.
ThreadPoolExecutor
class
We saw thread pools created by
a factory method, Executors.newFixedThreadPool,
and their methods were specified by an interface, ExecutorService.
You might wonder why there wasn't a class for thread pools, with its
own constructors and methods. In fact there is, and it is
ThreadPoolExecutor (and it
implements ExecutorService).
When you use newFixedThreadPool
it is quite likely that the thread pool it creates has class ThreadPoolExecutor, although
this is not guaranteed by the API. However, ThreadPoolExecutor has a lot of
configurable features and can be quite complicated. Using newFixedThreadPool and ExecutorService is a simple way
to get a basic thread pool in a standard default configuration.