Multiple Threads
Multiple Threads
Objects provide a way to divide a program
into independent sections. Often, you also need to turn a program into separate,
independently running subtasks.
Each of these independent subtasks is called a
thread, and you program as if each thread runs by itself and has the CPU
to itself. Some underlying mechanism is actually dividing up the CPU time for
you, but in general, you don’t have to think about it, which makes
programming with multiple threads a much easier task.
A process is a self-contained running program
with its own address space. A multitasking operating system is capable of
running more than one process (program) at a time, while making it look like
each one is chugging along on its own, by periodically providing CPU cycles to
each process. A thread is a single sequential flow of control within a process.
A single process can thus have multiple concurrently executing
threads.
There are many possible uses for multithreading, but
in general, you’ll have some part of your program tied to a particular
event or resource, and you don’t want to hang up the rest of your program
because of that. So you create a thread associated with that event or resource
and let it run independently of the main program. A good example is a
“quit” button—you don’t want to be forced to poll the
quit button in every piece of code you write in your program and yet you want
the quit button to be responsive, as if you were checking it regularly.
In fact, one of the most immediately compelling reasons for multithreading is to
produce a responsive user interface.
Responsive user interfaces
As a starting point, consider a program that performs
some CPU-intensive operation and thus ends up ignoring user input and being
unresponsive. This one, a combined applet/application, will simply display the
result of a running counter:
//:
c14:Counter1.java
//
A non-responsive user interface.
//
<applet code=Counter1 width=300 height=100>
//
</applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class
Counter1
extends
JApplet {
private
int
count = 0;
private
JButton
start =
new
JButton("Start"),
onOff =
new
JButton("Toggle");
private
JTextField t =
new
JTextField(10);
private
boolean
runFlag =
true;
public
void
init() {
Container cp = getContentPane();
cp.setLayout(new
FlowLayout());
cp.add(t);
start.addActionListener(new
StartL());
cp.add(start);
onOff.addActionListener(new
OnOffL());
cp.add(onOff);
}
public
void
go() {
while
(true)
{
try
{
Thread.sleep(100);
}
catch(InterruptedException
e) {
System.err.println("Interrupted");
}
if
(runFlag)
t.setText(Integer.toString(count++));
}
}
class
StartL
implements
ActionListener {
public
void
actionPerformed(ActionEvent e) {
go();
}
}
class
OnOffL
implements
ActionListener {
public
void
actionPerformed(ActionEvent e) {
runFlag = !runFlag;
}
}
public
static
void
main(String[] args) {
Console.run(new
Counter1(), 300, 100);
}
}
///:~
At this point, the Swing and applet code should be
reasonably familiar from Chapter 13. The go( ) method is where the
program stays busy: it puts the current value of count into the
JTextField t, then increments count.
Part of the infinite loop inside go( ) is
to call sleep( ). sleep( ) must be associated with a
Thread object, and it turns out that every application has some
thread associated with it. (Indeed, Java is based on threads and there are
always some running along with your application.) So regardless of whether
you’re explicitly using threads, you can produce the current thread used
by your program with Thread and the static sleep( ) method.
Note that sleep( ) can throw an
InterruptedException, although throwing such an exception is considered a
hostile way to break from a thread and should be discouraged. (Once again,
exceptions are for exceptional conditions, not normal flow of control.)
Interrupting a sleeping thread is included to support a future language feature.
When the start button is pressed,
go( ) is invoked. On examining go( ), you might naively
think (as I did) that it should allow multithreading because it goes to sleep.
That is, while the method is asleep, it seems like the CPU could be busy
monitoring other button presses. But it turns out that the real problem is that
go( ) never returns, since it’s in an infinite loop, and this
means that actionPerformed( ) never returns. Since you’re
stuck inside actionPerformed( ) for the first keypress, the program
can’t handle any other events. (To get out, you must somehow kill the
process; the easiest way to do this is to press Control-C in the console window,
if you started it from the console. If you start it via the browser, you have to
kill the browser window.)
The basic problem here is that go( ) needs
to continue performing its operations, and at the same time it needs to return
so that actionPerformed( ) can complete and the user interface can
continue responding to the user. But in a conventional method like
go( ) it cannot continue and at the same time return control
to the rest of the program. This sounds like an impossible thing to accomplish,
as if the CPU must be in two places at once, but this is precisely the illusion
that threading provides.
The thread model (and its programming support in Java)
is a programming convenience to simplify juggling several operations at the same
time within a single program. With threads, the CPU will pop around and give
each thread some of its time. Each thread has the consciousness of constantly
having the CPU to itself, but the CPU’s time is actually sliced between
all the threads. The exception to this is if your program is running on multiple
CPUs. But one of the great things about threading is that you are abstracted
away from this layer, so your code does not need to know whether it is actually
running on a single CPU or many. Thus, threads are a way to create transparently
scalable programs.
Threading reduces computing efficiency somewhat, but
the net improvement in program design, resource balancing, and user convenience
is often quite valuable. Of course, if you have more than one CPU, then the
operating system can dedicate each CPU to a set of threads or even a single
thread and the whole program can run much faster. Multitasking and
multithreading tend to be the most reasonable ways to utilize multiprocessor
systems.
Inheriting from Thread
The simplest way to create a thread is to inherit from
class Thread, which has all the wiring necessary to create and run
threads. The most important method for Thread is run( ),
which you must override to make the thread do your bidding. Thus,
run( ) is the code that will be executed
“simultaneously” with the other threads in a program.
The following example creates any number of threads
that it keeps track of by assigning each thread a unique number, generated with
a static variable. The Thread’s run( ) method is
overridden to count down each time it passes through its loop and to finish when
the count is zero (at the point when run( ) returns, the thread is
terminated).
//:
c14:SimpleThread.java
//
Very simple Threading example.
public
class
SimpleThread
extends
Thread {
private
int
countDown = 5;
private
static
int
threadCount = 0;
private
int
threadNumber = ++threadCount;
public
SimpleThread() {
System.out.println("Making
" + threadNumber);
}
public
void
run() {
while(true)
{
System.out.println("Thread
" +
threadNumber +
"("
+ countDown +
")");
if(--countDown
== 0)
return;
}
}
public
static
void
main(String[] args) {
for(int
i = 0; i < 5; i++)
new
SimpleThread().start();
System.out.println("All Threads
Started");
}
}
///:~
A run( ) method virtually always has some
kind of loop that continues until the thread is no longer necessary, so you must
establish the condition on which to break out of this loop (or, in the case
above, simply return from run( )). Often, run( )
is cast in the form of an infinite loop, which means that, barring some external
factor that causes run( ) to terminate, it will continue forever.
In main( ) you can see a number of threads
being created and run. The start( ) method in the Thread
class performs special initialization for the thread and then calls
run( ). So the steps are: the constructor is called to build the
object, then start( ) configures the thread and calls
run( ). If you don’t call start( ) (which you can
do in the constructor, if that’s appropriate) the thread will never be
started.
The output for one run of this program (it will be
different from one run to another) is:
Making
1
Making
2
Making
3
Making
4
Making
5
Thread
1(5)
Thread
1(4)
Thread
1(3)
Thread
1(2)
Thread
2(5)
Thread
2(4)
Thread
2(3)
Thread
2(2)
Thread
2(1)
Thread
1(1)
All
Threads Started
Thread
3(5)
Thread
4(5)
Thread
4(4)
Thread
4(3)
Thread
4(2)
Thread
4(1)
Thread
5(5)
Thread
5(4)
Thread
5(3)
Thread
5(2)
Thread
5(1)
Thread
3(4)
Thread
3(3)
Thread
3(2)
Thread
3(1)
You’ll notice that nowhere in this example is
sleep( ) called, and yet the output indicates that each thread gets
a portion of the CPU’s time in which to execute. This shows that
sleep( ), while it relies on the existence of a thread in order to
execute, is not involved with either enabling or disabling threading. It’s
simply another method.
You can also see that the threads are not run in the
order that they’re created. In fact, the order that the CPU attends to an
existing set of threads is indeterminate, unless you go in and adjust the
priorities using Thread’s setPriority( )
method.
When main( ) creates the Thread
objects it isn’t capturing the references for any of them. An ordinary
object would be fair game for garbage collection, but not a Thread. Each
Thread “registers” itself so there is actually a reference to
it someplace and the garbage collector can’t clean it up.
Threading for a responsive
interface
Now it’s possible to solve the problem in
Counter1.java with a thread. The trick is to place the subtask—that
is, the loop that’s inside go( )—inside the
run( ) method of a thread. When the user presses the start
button, the thread is started, but then the creation of the thread
completes, so even though the thread is running, the main job of the program
(watching for and responding to user-interface events) can continue.
Here’s the solution:
//:
c14:Counter2.java
//
A responsive user interface with threads.
//
<applet code=Counter2 width=300 height=100>
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class
Counter2
extends
JApplet {
private
class
SeparateSubTask
extends
Thread {
private
int
count = 0;
private
boolean
runFlag =
true;
SeparateSubTask() { start(); }
void
invertFlag() { runFlag = !runFlag; }
public
void
run() {
while
(true)
{
try
{
sleep(100);
}
catch(InterruptedException
e) {
System.err.println("Interrupted");
}
if(runFlag)
t.setText(Integer.toString(count++));
}
}
}
private
SeparateSubTask sp =
null;
private
JTextField t =
new
JTextField(10);
private
JButton
start =
new
JButton("Start"),
onOff =
new
JButton("Toggle");
class
StartL
implements
ActionListener {
public
void
actionPerformed(ActionEvent e) {
if(sp
==
null)
sp =
new
SeparateSubTask();
}
}
class
OnOffL
implements
ActionListener {
public
void
actionPerformed(ActionEvent e) {
if(sp
!=
null)
sp.invertFlag();
}
}
public
void
init() {
Container cp = getContentPane();
cp.setLayout(new
FlowLayout());
cp.add(t);
start.addActionListener(new
StartL());
cp.add(start);
onOff.addActionListener(new
OnOffL());
cp.add(onOff);
}
public
static
void
main(String[] args) {
Console.run(new
Counter2 (), 300, 100);
}
}
///:~
Counter2 is a straightforward program, whose
only job is to set up and maintain the user interface. But now, when the user
presses the start button, the event-handling code does not call a method.
Instead a thread of class SeparateSubTask is created, and then the
Counter2 event loop continues.
The class SeparateSubTask is a simple extension
of Thread with a constructor that runs the thread by calling
start( ), and a run( ) that essentially contains the
“go( )” code from Counter1.java.
Because SeparateSubTask is an inner class, it
can directly access the JTextField t in Counter2; you can see this
happening inside run( ). The t field in the outer class is
private since SeparateSubTask can access it without getting any
special permission—and it’s always good to make fields “as
private as possible” so they cannot be accidentally changed by
forces outside your class.
When you press the onOff button it toggles the
runFlag inside the SeparateSubTask object. That thread (when it
looks at the flag) can then start and stop itself. Pressing the onOff
button produces an apparently instant response. Of course, the response
isn’t really instant, not like that of a system that’s driven by
interrupts. The counter stops only when the thread has the CPU and notices that
the flag has changed.
You can see that the inner class
SeparateSubTask is private, which means that its fields and
methods can be given default access (except for run( ), which must
be public since it is public in the base class). The private
inner class is not accessible to anyone but Counter2, and the two
classes are tightly coupled. Anytime you notice classes that appear to have high
coupling with each other, consider the coding and maintenance improvements you
might get by using inner classes.
Combining the thread
with the main
class
In the example above you can see that the thread class
is separate from the program’s main class. This makes a lot of sense and
is relatively easy to understand. There is, however, an alternate form that you
will often see used that is not so clear but is usually more concise (which
probably accounts for its popularity). This form combines the main program class
with the thread class by making the main program class a thread. Since for a GUI
program the main program class must be inherited from either Frame or
Applet, an interface must be used to paste on the additional
functionality. This interface is called Runnable, and it contains the
same basic method that Thread does. In fact, Thread also
implements Runnable, which specifies only that there be a
run( ) method.
The use of the combined program/thread is not
quite so obvious. When you start the program, you create an object that’s
Runnable, but you don’t start the thread. This must be done
explicitly. You can see this in the following program, which reproduces the
functionality of Counter2:
//:
c14:Counter3.java
//
Using the Runnable interface to turn the
//
main class into a thread.
//
<applet code=Counter3 width=300 height=100>
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class
Counter3
extends
JApplet
implements
Runnable {
private
int
count = 0;
private
boolean
runFlag =
true;
private
Thread selfThread =
null;
private
JButton
start =
new
JButton("Start"),
onOff =
new
JButton("Toggle");
private
JTextField t =
new
JTextField(10);
public
void
run() {
while
(true)
{
try
{
selfThread.sleep(100);
}
catch(InterruptedException
e) {
System.err.println("Interrupted");
}
if(runFlag)
t.setText(Integer.toString(count++));
}
}
class
StartL
implements
ActionListener {
public
void
actionPerformed(ActionEvent e) {
if(selfThread
==
null)
{
selfThread =
new
Thread(Counter3.this);
selfThread.start();
}
}
}
class
OnOffL
implements
ActionListener {
public
void
actionPerformed(ActionEvent e) {
runFlag = !runFlag;
}
}
public
void
init() {
Container cp = getContentPane();
cp.setLayout(new
FlowLayout());
cp.add(t);
start.addActionListener(new
StartL());
cp.add(start);
onOff.addActionListener(new
OnOffL());
cp.add(onOff);
}
public
static
void
main(String[] args) {
Console.run(new
Counter3(), 300, 100);
}
}
///:~
Now the run( ) is inside the class, but
it’s still dormant after init( ) completes. When you press the
start button, the thread is created (if it doesn’t already exist)
in the somewhat obscure expression:
new
Thread(Counter3.this);
When something has a Runnable interface, it
simply means that it has a run( ) method, but there’s nothing
special about that—it doesn’t produce any innate threading
abilities, like those of a class inherited from Thread. So to produce a
thread from a Runnable object, you must create a separate Thread
object as shown above, handing the Runnable object to the special
Thread constructor. You can then call start( ) for that
thread:
selfThread.start();
This performs the usual initialization and then calls
run( ).
The convenient aspect about the Runnable
interface is that everything belongs to the same class. If you need to
access something, you simply do it without going through a separate object.
However, as you saw in the previous example, this access is just as easy using
an inner class.
Making many threads
Consider the creation of many different threads. You
can’t do this with the previous example, so you must go back to having
separate classes inherited from Thread to encapsulate the
run( ). But this is a more general solution and easier to
understand, so while the previous example shows a coding style you’ll
often see, I can’t recommend it for most cases because it’s just a
little bit more confusing and less flexible.
The following example repeats the form of the examples
above with counters and toggle buttons. But now all the information for a
particular counter, including the button and text field, is inside its own
object that is inherited from Thread. All the fields in Ticker are
private, which means that the Ticker implementation can be changed
at will, including the quantity and type of data components to acquire and
display information. When a Ticker object is created, the constructor
adds its visual components to the content pane of the outer
object:
//:
c14:Counter4.java
//
By keeping your thread as a distinct class,
//
you can have as many threads as you want.
//
<applet code=Counter4 width=200 height=600>
//
<param name=size value="12"></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class
Counter4
extends
JApplet {
private
JButton start =
new
JButton("Start");
private
boolean
started =
false;
private
Ticker[] s;
private
boolean
isApplet =
true;
private
int
size = 12;
class
Ticker
extends
Thread {
private
JButton b =
new
JButton("Toggle");
private
JTextField t =
new
JTextField(10);
private
int
count = 0;
private
boolean
runFlag =
true;
public
Ticker() {
b.addActionListener(new
ToggleL());
JPanel p =
new
JPanel();
p.add(t);
p.add(b);
// Calls
JApplet.getContentPane().add():
getContentPane().add(p);
}
class
ToggleL
implements
ActionListener {
public
void
actionPerformed(ActionEvent e) {
runFlag = !runFlag;
}
}
public
void
run() {
while
(true)
{
if
(runFlag)
t.setText(Integer.toString(count++));
try
{
sleep(100);
}
catch(InterruptedException
e) {
System.err.println("Interrupted");
}
}
}
}
class
StartL
implements
ActionListener {
public
void
actionPerformed(ActionEvent e) {
if(!started)
{
started =
true;
for
(int
i = 0; i < s.length; i++)
s[i].start();
}
}
}
public
void
init() {
Container cp = getContentPane();
cp.setLayout(new
FlowLayout());
// Get parameter "size" from Web
page:
if
(isApplet) {
String sz =
getParameter("size");
if(sz
!=
null)
size = Integer.parseInt(sz);
}
s =
new
Ticker[size];
for
(int
i = 0; i < s.length; i++)
s[i] =
new
Ticker();
start.addActionListener(new
StartL());
cp.add(start);
}
public
static
void
main(String[] args) {
Counter4 applet =
new
Counter4();
// This isn't an applet, so set
the flag and
// produce the parameter values
from args:
applet.isApplet =
false;
if(args.length
!= 0)
applet.size = Integer.parseInt(args[0]);
Console.run(applet, 200, applet.size * 50);
}
}
///:~
Ticker contains not only its threading
equipment but also the way to control and display the thread. You can create as
many threads as you want without explicitly creating the windowing
components.
In Counter4 there’s an array of
Ticker objects called s. For maximum flexibility, the size of this
array is initialized by reaching out into the Web page using applet parameters.
Here’s what the size parameter looks like on the page, embedded inside the
applet tag:
<param
name=size
value="20">
The param, name, and value are
all HTML keywords. name is what you’ll be referring to in your
program, and value can be any string, not just something that resolves to
a number
You’ll notice that the determination of the size
of the array s is done inside init( ), and not as part of an
inline definition of s. That is, you cannot say as part of the
class definition (outside of any methods):
int
size =
Integer.parseInt(getParameter("size"));
Ticker[]
s =
new
Ticker[size];
You can compile this, but you’ll get a strange
“null-pointer exception” at run-time. It works fine if you move the
getParameter( ) initialization inside of init( ). The
applet framework performs the necessary startup to grab the parameters before
entering init( ).
In addition, this code is set up to be either an
applet or an application. When it’s an application the size
argument is extracted from the command line (or a default value is provided).
Once the size of the array is established, new
Ticker objects are created; as part of the Ticker constructor the
button and text field for each Ticker is added to the applet.
Pressing the start button means looping through
the entire array of Tickers and calling start( ) for each
one. Remember, start( ) performs necessary thread initialization and
then calls run( ) for that thread.
The ToggleL listener simply inverts the flag in
Ticker and when the associated thread next takes note it can react
accordingly.
One value of this example is that it allows you to
easily create large sets of independent subtasks and to monitor their behavior.
In this case, you’ll see that as the number of subtasks gets larger, your
machine will probably show more divergence in the displayed numbers because of
the way that the threads are served.
You can also experiment to discover how important the
sleep(100) is inside Ticker.run( ). If you remove the
sleep( ), things will work fine until you press a toggle button.
Then that particular thread has a false runFlag and the
run( ) is just tied up in a tight infinite loop, which appears
difficult to break during multithreading, so the responsiveness and speed of the
program really bogs down.
Daemon threads
A “daemon” thread is one that is supposed
to provide a general service in the background as long as the program is
running, but is not part of the essence of the program. Thus, when all of the
non-daemon threads complete, the program is terminated. Conversely, if there are
any non-daemon threads still running, the program doesn’t terminate.
(There is, for instance, a thread that runs main( ).)
You can find out if a thread is a daemon by calling
isDaemon( ), and you can turn the “daemonhood” of a
thread on and off with setDaemon( ). If a thread is a daemon, then
any threads it creates will automatically be daemons.
The following example demonstrates daemon
threads:
//:
c14:Daemons.java
//
Daemonic behavior.
import
java.io.*;
class
Daemon
extends
Thread {
private
static
final
int
SIZE = 10;
private
Thread[] t =
new
Thread[SIZE];
public
Daemon() {
setDaemon(true);
start();
}
public
void
run() {
for(int
i = 0; i < SIZE; i++)
t[i] =
new
DaemonSpawn(i);
for(int
i = 0; i < SIZE; i++)
System.out.println(
"t["
+ i + "].isDaemon() =
"
+ t[i].isDaemon());
while(true)
yield();
}
}
class
DaemonSpawn
extends
Thread {
public
DaemonSpawn(int
i) {
System.out.println(
"DaemonSpawn
" + i +
"
started");
start();
}
public
void
run() {
while(true)
yield();
}
}
public
class
Daemons {
public
static
void
main(String[] args)
throws
IOException {
Thread d =
new
Daemon();
System.out.println(
"d.isDaemon() =
" + d.isDaemon());
// Allow the daemon threads
to
// finish their startup
processes:
System.out.println("Press any
key");
System.in.read();
}
}
///:~
The Daemon thread sets its daemon flag to
“true” and then spawns a bunch of other threads to show that they
are also daemons. Then it goes into an infinite loop that calls
yield( ) to give up control to the other processes. In an earlier
version of this program, the infinite loops would increment int counters,
but this seemed to bring the whole program to a stop. Using yield( )
makes the program quite peppy.
There’s nothing to keep the program from
terminating once main( ) finishes its job, since there are nothing
but daemon threads running. So that you can see the results of starting all the
daemon threads, System.in is set up to read so the program waits for a
keypress before terminating. Without this you see only some of the results from
the creation of the daemon threads. (Try replacing the read( ) code
with sleep( ) calls of various lengths to see this behavior.)
|