Thinking
in Java
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.
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.
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.
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.
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[70].
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.
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.)
You
can think of a single-threaded program as one lonely entity moving around
through your problem space and doing one thing at a time. Because there’s only one
entity, you never have to think about the problem of two entities trying to use
the same resource at the same time, like two people trying to park in the same
space, walk through a door at the same time, or even talk at the same time.
With
multithreading, things aren’t lonely anymore, but you now have the possibility
of two or more threads trying to use the same limited resource at once.
Colliding over a resource must be prevented or else you’ll have two threads
trying to access the same bank account at the same time, print to the same
printer, or adjust the same valve, etc.
Consider
a variation on the counters that have been used so far in this chapter. In the
following example, each thread contains two counters that are incremented and
displayed inside run( ). In addition, there’s another thread of
class Watcher that is watching the counters to see if they’re always
equivalent. This seems like a needless activity, since looking at the code it
appears obvious that the counters will always be the same. But that’s where the
surprise comes in. Here’s the first version of the program:
//: c14:Sharing1.java
// Problems with resource sharing while threading.
// <applet code=Sharing1 width=350 height=500>
// <param name=size value="12">
// <param name=watchers value="15">
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
public class Sharing1 extends JApplet {
private static int accessCount = 0;
private static JTextField aCount =
new JTextField("0", 7);
public static void incrementAccess() {
accessCount++;
aCount.setText(Integer.toString(accessCount));
}
private JButton
start = new JButton("Start"),
watcher = new JButton("Watch");
private boolean isApplet = true;
private int numCounters = 12;
private int numWatchers = 15;
private TwoCounter[] s;
class TwoCounter extends Thread {
private boolean started = false;
private JTextField
t1 = new JTextField(5),
t2 = new JTextField(5);
private JLabel l =
new JLabel("count1 == count2");
private int count1 = 0, count2 = 0;
// Add the display components as a panel:
public TwoCounter() {
JPanel p = new JPanel();
p.add(t1);
p.add(t2);
p.add(l);
getContentPane().add(p);
}
public void start() {
if(!started) {
started = true;
super.start();
}
}
public void run() {
while (true) {
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
try {
sleep(500);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
public void synchTest() {
Sharing1.incrementAccess();
if(count1 != count2)
l.setText("Unsynched");
}
}
class Watcher extends Thread {
public Watcher() { start(); }
public void run() {
while(true) {
for(int i = 0; i < s.length; i++)
s[i].synchTest();
try {
sleep(500);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < s.length; i++)
s[i].start();
}
}
class WatcherL implements ActionListener {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < numWatchers; i++)
new Watcher();
}
}
public void init() {
if(isApplet) {
String counters = getParameter("size");
if(counters != null)
numCounters = Integer.parseInt(counters);
String watchers = getParameter("watchers");
if(watchers != null)
numWatchers = Integer.parseInt(watchers);
}
s = new TwoCounter[numCounters];
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
for(int i = 0; i < s.length; i++)
s[i] = new TwoCounter();
JPanel p = new JPanel();
start.addActionListener(new StartL());
p.add(start);
watcher.addActionListener(new WatcherL());
p.add(watcher);
p.add(new JLabel("Access Count"));
p.add(aCount);
cp.add(p);
}
public static void main(String[] args) {
Sharing1 applet = new Sharing1();
// This isn't an applet, so set the flag and
// produce the parameter values from args:
applet.isApplet = false;
applet.numCounters =
(args.length == 0 ? 12 :
Integer.parseInt(args[0]));
applet.numWatchers =
(args.length < 2 ? 15 :
Integer.parseInt(args[1]));
Console.run(applet, 350,
applet.numCounters * 50);
}
} ///:~
As
before, each counter contains its own display components: two text fields and a
label that initially indicates that the counts are equivalent. These components
are added to the content pane of the outer class object in the TwoCounter
constructor.
Because
a TwoCounter thread is started via a keypress by the user, it’s possible
that start( ) could be called more than once. It’s illegal for Thread.start( )
to be called more than once for a thread (an exception is thrown). You can see
the machinery to prevent this in the started flag and the overridden start( )
method.
In
run( ), count1 and count2 are incremented and
displayed in a manner that would seem to keep them identical. Then sleep( )
is called; without this call the program balks because it becomes hard for the
CPU to swap tasks.
The
synchTest( ) method performs the apparently useless activity of
checking to see if count1 is equivalent to count2; if they are
not equivalent it sets the label to “Unsynched” to indicate this. But first, it
calls a static member of the class Sharing1 that increments and displays
an access counter to show how many times this check has occurred successfully.
(The reason for this will become apparent in later variations of this example.)
The
Watcher class is a thread whose job is to call synchTest( )
for all of the TwoCounter objects that are active. It does this by
stepping through the array that’s kept in the Sharing1 object. You can
think of the Watcher as constantly peeking over the shoulders of the TwoCounter
objects.
Sharing1
contains an array of TwoCounter objects that it initializes in init( )
and starts as threads when you press the “start” button. Later, when you press
the “Watch” button, one or more watchers are created and freed upon the
unsuspecting TwoCounter threads.
Note
that to run this as an applet in a browser, your applet tag will need to
contain the lines:
<param name=size value="20">
<param name=watchers value="1">
You
can experiment by changing the width, height, and parameters to suit your
tastes. By changing the size and watchers you’ll change the
behavior of the program. This program is set up to run as a stand-alone
application by pulling the arguments from the command line (or providing
defaults).
Here’s
the surprising part. In TwoCounter.run( ), the infinite loop is
just repeatedly passing over the adjacent lines:
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
(as
well as sleeping, but that’s not important here). When you run the program,
however, you’ll discover that count1 and count2 will be observed
(by the Watchers) to be unequal at times! This is because of the nature
of threads—they can be suspended at any time. So at times, the suspension
occurs between the execution of the above two lines, and the Watcher
thread happens to come along and perform the comparison at just this moment,
thus finding the two counters to be different.
This
example shows a fundamental problem with using threads. You never know when a
thread might be run. Imagine sitting at a table with a fork, about to spear the
last piece of food on your plate and as your fork reaches for it, the food
suddenly vanishes (because your thread was suspended and another thread came in
and stole the food). That’s the problem that you’re dealing with.
Sometimes
you don’t care if a resource is being accessed at the same time you’re trying
to use it (the food is on some other plate). But for multithreading to work,
you need some way to prevent two threads from accessing the same resource, at
least during critical periods.
Preventing
this kind of collision is simply a matter of putting a lock on a resource when
one thread is using it. The first thread that accesses a resource locks it, and
then the other threads cannot access that resource until it is unlocked, at
which time another thread locks and uses it, etc. If the front seat of the car
is the limited resource, the child who shouts “Dibs!” asserts the lock.
Java
has built-in support to prevent collisions over one kind of resource: the
memory in an object. Since you typically make the data elements of a class private
and access that memory only through methods, you can prevent collisions by
making a particular method synchronized. Only one thread at a time can
call a synchronized method for a particular object (although that thread
can call more than one of the object’s synchronized methods). Here are simple synchronized
methods:
synchronized void f() { /* ... */ }
synchronized void g(){ /* ... */ }
Each
object contains a single lock (also called a monitor) that is
automatically part of the object (you don’t have to write any special code).
When you call any synchronized method, that object is locked and no
other synchronized method of that object can be called until the first
one finishes and releases the lock. In the example above, if f( )
is called for an object, g( ) cannot be called for the same object
until f( ) is completed and releases the lock. Thus, there’s a
single lock that’s shared by all the synchronized methods of a
particular object, and this lock prevents common memory from being written by
more than one method at a time (i.e., more than one thread at a time).
There’s
also a single lock per class (as part of the Class object for the
class), so that synchronized static methods can lock each other
out from simultaneous access of static data on a class-wide basis.
Note
that if you want to guard some other resource from simultaneous access by
multiple threads, you can do so by forcing access to that resource through synchronized
methods.
Armed
with this new keyword it appears that the solution is at hand: we’ll simply use
the synchronized keyword for the methods in TwoCounter. The
following example is the same as the previous one, with the addition of the new
keyword:
//: c14:Sharing2.java
// Using the synchronized keyword to prevent
// multiple access to a particular resource.
// <applet code=Sharing2 width=350 height=500>
// <param name=size value="12">
// <param name=watchers value="15">
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
public class Sharing2 extends JApplet {
TwoCounter[] s;
private static int accessCount = 0;
private static JTextField aCount =
new JTextField("0", 7);
public static void incrementAccess() {
accessCount++;
aCount.setText(Integer.toString(accessCount));
}
private JButton
start = new JButton("Start"),
watcher = new JButton("Watch");
private boolean isApplet = true;
private int numCounters = 12;
private int numWatchers = 15;
class TwoCounter extends Thread {
private boolean started = false;
private JTextField
t1 = new JTextField(5),
t2 = new JTextField(5);
private JLabel l =
new JLabel("count1 == count2");
private int count1 = 0, count2 = 0;
public TwoCounter() {
JPanel p = new JPanel();
p.add(t1);
p.add(t2);
p.add(l);
getContentPane().add(p);
}
public void start() {
if(!started) {
started = true;
super.start();
}
}
public synchronized void run() {
while (true) {
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
try {
sleep(500);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
public synchronized void synchTest() {
Sharing2.incrementAccess();
if(count1 != count2)
l.setText("Unsynched");
}
}
class Watcher extends Thread {
public Watcher() { start(); }
public void run() {
while(true) {
for(int i = 0; i < s.length; i++)
s[i].synchTest();
try {
sleep(500);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < s.length; i++)
s[i].start();
}
}
class WatcherL implements ActionListener {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < numWatchers; i++)
new Watcher();
}
}
public void init() {
if(isApplet) {
String counters = getParameter("size");
if(counters != null)
numCounters = Integer.parseInt(counters);
String watchers = getParameter("watchers");
if(watchers != null)
numWatchers = Integer.parseInt(watchers);
}
s = new TwoCounter[numCounters];
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
for(int i = 0; i < s.length; i++)
s[i] = new TwoCounter();
JPanel p = new JPanel();
start.addActionListener(new StartL());
p.add(start);
watcher.addActionListener(new WatcherL());
p.add(watcher);
p.add(new Label("Access Count"));
p.add(aCount);
cp.add(p);
}
public static void main(String[] args) {
Sharing2 applet = new Sharing2();
// This isn't an applet, so set the flag and
// produce the parameter values from args:
applet.isApplet = false;
applet.numCounters =
(args.length == 0 ? 12 :
Integer.parseInt(args[0]));
applet.numWatchers =
(args.length < 2 ? 15 :
Integer.parseInt(args[1]));
Console.run(applet, 350,
applet.numCounters * 50);
}
} ///:~
You’ll
notice that both run( ) and synchTest( ) are synchronized.
If you synchronize only one of the methods, then the other is free to ignore
the object lock and can be called with impunity. This is an important point:
Every method that accesses a critical shared resource must be synchronized
or it won’t work right.
Now
a new issue arises. The Watcher can never get a peek at what’s going on
because the entire run( ) method has been synchronized, and
since run( ) is always running for each object the lock is always
tied up and synchTest( ) can never be called. You can see this
because the accessCount never changes.
What
we’d like for this example is a way to isolate only part of the code
inside run( ). The section of code you want to isolate this way is
called a critical section and you use the synchronized keyword in
a different way to set up a critical section. Java supports critical sections
with the synchronized block; this time synchronized is used to
specify the object whose lock is being used to synchronize the enclosed code:
synchronized(syncObject) {
// This code can be accessed
// by only one thread at a time
}
Before
the synchronized block can be entered, the lock must be acquired on syncObject.
If some other thread already has this lock, then the block cannot be entered
until the lock is given up.
The
Sharing2 example can be modified by removing the synchronized
keyword from the entire run( ) method and instead putting a synchronized
block around the two critical lines. But what object should be used as the
lock? The one that is already respected by synchTest( ), which is
the current object (this)! So the modified run( ) looks like
this:
public void run() {
while (true) {
synchronized(this) {
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
}
try {
sleep(500);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
This
is the only change that must be made to Sharing2.java, and you’ll see
that while the two counters are never out of synch (according to when the Watcher
is allowed to look at them), there is still adequate access provided to the Watcher
during the execution of run( ).
Of
course, all synchronization depends on programmer diligence: every piece of
code that can access a shared resource must be wrapped in an appropriate
synchronized block.
Since
having two methods write to the same piece of data never sounds like a
particularly good idea, it might seem to make sense for all methods to be
automatically synchronized and eliminate the synchronized keyword
altogether. (Of course, the example with a synchronized run( )
shows that this wouldn’t work either.) But it turns out that acquiring a lock
is not a cheap operation—it multiplies the cost of a method call (that is,
entering and exiting from the method, not executing the body of the method) by
a minimum of four times, and could be much more depending on your
implementation. So if you know that a particular method will not cause
contention problems it is expedient to leave off the synchronized keyword.
On the other hand, leaving off the synchronized keyword because you
think it is a performance bottleneck, and hoping that there aren’t any
collisions is an invitation to disaster.
Now
that you understand synchronization, you can take another look at JavaBeans.
Whenever you create a Bean, you must assume that it will run in a multithreaded
environment. This means that:
1.
Whenever possible, all the public methods of a Bean
should be synchronized. Of course, this incurs the synchronized
run-time overhead. If that’s a problem, methods that will not cause problems in
critical sections can be left un-synchronized, but keep in mind that
this is not always obvious. Methods that qualify tend to be small (such as getCircleSize( )
in the following example) and/or “atomic,” that is, the method call executes in
such a short amount of code that the object cannot be changed during execution.
Making such methods un-synchronized might not have a significant effect
on the execution speed of your program. You might as well make all public
methods of a Bean synchronized and remove the synchronized
keyword only when you know for sure that it’s necessary and that it makes a
difference.
2.
When firing a multicast event to a bunch of listeners
interested in that event, you must assume that listeners might be added or
removed while moving through the list.
The
first point is fairly easy to deal with, but the second point requires a little
more thought. Consider the BangBean.java example presented in the last
chapter. That ducked out of the multithreading question by ignoring the synchronized
keyword (which hadn’t been introduced yet) and making the event unicast. Here’s
that example modified to work in a multithreaded environment and to use
multicasting for events:
//: c14:BangBean2.java
// You should write your Beans this way so they
// can run in a multithreaded environment.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import com.bruceeckel.swing.*;
public class BangBean2 extends JPanel
implements Serializable {
private int xm, ym;
private int cSize = 20; // Circle size
private String text = "Bang!";
private int fontSize = 48;
private Color tColor = Color.red;
private ArrayList actionListeners =
new ArrayList();
public BangBean2() {
addMouseListener(new ML());
addMouseMotionListener(new MM());
}
public synchronized int getCircleSize() {
return cSize;
}
public synchronized void
setCircleSize(int newSize) {
cSize = newSize;
}
public synchronized String getBangText() {
return text;
}
public synchronized void
setBangText(String newText) {
text = newText;
}
public synchronized int getFontSize() {
return fontSize;
}
public synchronized void
setFontSize(int newSize) {
fontSize = newSize;
}
public synchronized Color getTextColor() {
return tColor;
}
public synchronized void
setTextColor(Color newColor) {
tColor = newColor;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.black);
g.drawOval(xm - cSize/2, ym - cSize/2,
cSize, cSize);
}
// This is a multicast listener, which is
// more typically used than the unicast
// approach taken in BangBean.java:
public synchronized void
addActionListener(ActionListener l) {
actionListeners.add(l);
}
public synchronized void
removeActionListener(ActionListener l) {
actionListeners.remove(l);
}
// Notice this isn't synchronized:
public void notifyListeners() {
ActionEvent a =
new ActionEvent(BangBean2.this,
ActionEvent.ACTION_PERFORMED, null);
ArrayList lv = null;
// Make a shallow copy of the List in case
// someone adds a listener while we're
// calling listeners:
synchronized(this) {
lv = (ArrayList)actionListeners.clone();
}
// Call all the listener methods:
for(int i = 0; i < lv.size(); i++)
((ActionListener)lv.get(i))
.actionPerformed(a);
}
class ML extends MouseAdapter {
public void mousePressed(MouseEvent e) {
Graphics g = getGraphics();
g.setColor(tColor);
g.setFont(
new Font(
"TimesRoman", Font.BOLD, fontSize));
int width =
g.getFontMetrics().stringWidth(text);
g.drawString(text,
(getSize().width - width) /2,
getSize().height/2);
g.dispose();
notifyListeners();
}
}
class MM extends MouseMotionAdapter {
public void mouseMoved(MouseEvent e) {
xm = e.getX();
ym = e.getY();
repaint();
}
}
public static void main(String[] args) {
BangBean2 bb = new BangBean2();
bb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
System.out.println("ActionEvent" + e);
}
});
bb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
System.out.println("BangBean2 action");
}
});
bb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e){
System.out.println("More action");
}
});
Console.run(bb, 300, 300);
}
} ///:~
Adding
synchronized to the methods is an easy change. However, notice in addActionListener( )
and removeActionListener( ) that the ActionListeners are now
added to and removed from an ArrayList, so you can have as many as you
want.
You
can see that the method notifyListeners( ) is not synchronized.
It can be called from more than one thread at a time. It’s also possible for addActionListener( )
or removeActionListener( ) to be called in the middle of a call to notifyListeners( ),
which is a problem since it traverses the ArrayList actionListeners. To
alleviate the problem, the ArrayList is cloned inside a synchronized
clause and the clone is traversed (see Appendix A for details of cloning). This
way the original ArrayList can be manipulated without impact on notifyListeners( ).
The
paintComponent( ) method is also not synchronized. Deciding
whether to synchronize overridden methods is not as clear as when you’re just
adding your own methods. In this example it turns out that paint( )
seems to work OK whether it’s synchronized or not. But the issues you
must consider are:
1.
Does the method modify the state of “critical” variables
within the object? To discover whether the variables are “critical” you must
determine whether they will be read or set by other threads in the program. (In
this case, the reading or setting is virtually always accomplished via synchronized
methods, so you can just examine those.) In the case of paint( ),
no modification takes place.
2.
Does the method depend on the state of these “critical”
variables? If a synchronized method modifies a variable that your method
uses, then you might very well want to make your method synchronized as
well. Based on this, you might observe that cSize is changed by synchronized
methods and therefore paint( ) should be synchronized. Here,
however, you can ask “What’s the worst thing that will happen if cSize
is changed during a paint( )?” When you see that it’s nothing too
bad, and a transient effect at that, you can decide to leave paint( )
un-synchronized to prevent the extra overhead from the synchronized
method call.
3.
A third clue is to notice whether the base-class version
of paint( ) is synchronized, which it isn’t. This isn’t an
airtight argument, just a clue. In this case, for example, a field that is
changed via synchronized methods (that is cSize) has been mixed
into the paint( ) formula and might have changed the situation.
Notice, however, that synchronized doesn’t inherit—that is, if a method
is synchronized in the base class then it is not automatically synchronized
in the derived class overridden version.
The
test code in TestBangBean2 has been modified from that in the previous
chapter to demonstrate the multicast ability of BangBean2 by adding
extra listeners.
A
thread can be in any one of four states:
1.
New: The thread object has been
created but it hasn’t been started yet so it cannot run.
2.
Runnable: This means that a thread can
be run when the time-slicing mechanism has CPU cycles available for the thread.
Thus, the thread might or might not be running, but there’s nothing to prevent
it from being run if the scheduler can arrange it; it’s not dead or blocked.
3.
Dead: The normal way for a thread to
die is by returning from its run( ) method. You can also call stop( ),
but this throws an exception that’s a subclass of Error (which means you
aren’t forced to put the call in a try block). Remember that throwing an
exception should be a special event and not part of normal program execution;
thus the use of stop( ) is deprecated in Java 2. There’s also a destroy( )
method (which has never been implemented) that you should never call if you can
avoid it since it’s drastic and doesn’t release object locks.
4.
Blocked: The thread could be run but
there’s something that prevents it. While a thread is in the blocked state the
scheduler will simply skip over it and not give it any CPU time. Until a thread
reenters the runnable state it won’t perform any operations.
The
blocked state is the most interesting one, and is worth further examination. A
thread can become blocked for five reasons:
1.
You’ve put the thread to sleep by calling sleep(milliseconds),
in which case it will not be run for the specified time.
2.
You’ve suspended the execution of the thread with suspend( ).
It will not become runnable again until the thread gets the resume( )
message (these are deprecated in Java 2, and will be examined further).
3.
You’ve suspended the execution of the thread with wait( ).
It will not become runnable again until the thread gets the notify( )
or notifyAll( ) message. (Yes, this looks just like number 2, but
there’s a distinct difference that will be revealed.)
4.
The thread is waiting for some I/O to complete.
5.
The thread is trying to call a synchronized method
on another object, and that object’s lock is not available.
You
can also call yield( ) (a method of the Thread class) to
voluntarily give up the CPU so that other threads can run. However, the same
thing happens if the scheduler decides that your thread has had enough time and
jumps to another thread. That is, nothing prevents the scheduler from moving
your thread and giving time to some other thread. When a thread is blocked,
there’s some reason that it cannot continue running.
The
following example shows all five ways of becoming blocked. It all exists in a
single file called Blocking.java, but it will be examined here in
discrete pieces. (You’ll notice the “Continued” and “Continuing” tags that
allow the code extraction tool to piece everything together.)
Because
this example demonstrates some deprecated methods, you will get
deprecation messages when it is compiled.
First,
the basic framework:
//: c14:Blocking.java
// Demonstrates the various ways a thread
// can be blocked.
// <applet code=Blocking width=350 height=550>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import com.bruceeckel.swing.*;
//////////// The basic framework ///////////
class Blockable extends Thread {
private Peeker peeker;
protected JTextField state = new JTextField(30);
protected int i;
public Blockable(Container c) {
c.add(state);
peeker = new Peeker(this, c);
}
public synchronized int read() { return i; }
protected synchronized void update() {
state.setText(getClass().getName()
+ " state: i = " + i);
}
public void stopPeeker() {
// peeker.stop(); Deprecated in Java 1.2
peeker.terminate(); // The preferred approach
}
}
class Peeker extends Thread {
private Blockable b;
private int session;
private JTextField status = new JTextField(30);
private boolean stop = false;
public Peeker(Blockable b, Container c) {
c.add(status);
this.b = b;
start();
}
public void terminate() { stop = true; }
public void run() {
while (!stop) {
status.setText(b.getClass().getName()
+ " Peeker " + (++session)
+ "; value = " + b.read());
try {
sleep(100);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
} ///:Continued
The
Blockable class is meant to be a base class for all the classes in this
example that demonstrate blocking. A Blockable object contains a
JTextField called state that is used to display information about
the object. The method that displays this information is update( ).
You can see it uses getClass( ).getName( ) to produce the name
of the class instead of just printing it out; this is because update( )
cannot know the exact name of the class it is called for, since it will be a
class derived from Blockable.
The
indicator of change in Blockable is an int i, which will be
incremented by the run( ) method of the derived class.
There’s
a thread of class Peeker that is started for each Blockable
object, and the Peeker’s job is to watch its associated Blockable
object to see changes in i by calling read( ) and reporting
them in its status JTextField. This is important: Note that read( )
and update( ) are both synchronized, which means they
require that the object lock be free.
The
first test in this program is with sleep( ):
///:Continuing
///////////// Blocking via sleep() ///////////
class Sleeper1 extends Blockable {
public Sleeper1(Container c) { super(c); }
public synchronized void run() {
while(true) {
i++;
update();
try {
sleep(1000);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class Sleeper2 extends Blockable {
public Sleeper2(Container c) { super(c); }
public void run() {
while(true) {
change();
try {
sleep(1000);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
public synchronized void change() {
i++;
update();
}
} ///:Continued
In
Sleeper1 the entire run( ) method is synchronized.
You’ll see that the Peeker associated with this object will run along
merrily until you start the thread, and then the Peeker stops
cold. This is one form of blocking: since Sleeper1.run( ) is synchronized,
and once the thread starts it’s always inside run( ), the method
never gives up the object lock and the Peeker is blocked.
Sleeper2
provides a solution by making run( ) un-synchronized. Only
the change( ) method is synchronized, which means that while
run( ) is in sleep( ), the Peeker can access the
synchronized method it needs, namely read( ). Here you’ll
see that the Peeker continues running when you start the Sleeper2
thread.
The
next part of the example introduces the concept of suspension. The Thread
class has a method suspend( ) to temporarily stop the thread and resume( )
that restarts it at the point it was halted. resume( ) must be
called by some thread outside the suspended one, and in this case there’s a
separate class called Resumer that does just that. Each of the classes
demonstrating suspend/resume has an associated resumer:
///:Continuing
/////////// Blocking via suspend() ///////////
class SuspendResume extends Blockable {
public SuspendResume(Container c) {
super(c);
new Resumer(this);
}
}
class SuspendResume1 extends SuspendResume {
public SuspendResume1(Container c) { super(c);}
public synchronized void run() {
while(true) {
i++;
update();
suspend(); // Deprecated in Java 1.2
}
}
}
class SuspendResume2 extends SuspendResume {
public SuspendResume2(Container c) { super(c);}
public void run() {
while(true) {
change();
suspend(); // Deprecated in Java 1.2
}
}
public synchronized void change() {
i++;
update();
}
}
class Resumer extends Thread {
private SuspendResume sr;
public Resumer(SuspendResume sr) {
this.sr = sr;
start();
}
public void run() {
while(true) {
try {
sleep(1000);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
sr.resume(); // Deprecated in Java 1.2
}
}
} ///:Continued
SuspendResume1 also
has a synchronized run( ) method. Again, when you start this thread
you’ll see that its associated Peeker gets blocked waiting for the lock
to become available, which never happens. This is fixed as before in SuspendResume2,
which does not synchronize the entire run( ) method but
instead uses a separate synchronized change( ) method.
You
should be aware that Java 2 deprecates the use of suspend( ) and resume( ),
because suspend( ) holds the object’s lock and is thus
deadlock-prone. That is, you can easily get a number of locked objects waiting
on each other, and this will cause your program to freeze. Although you might
see them used in older programs you should not use suspend( ) and resume( ).
The proper solution is described later in this chapter.
In
the first two examples, it’s important to understand that both sleep( )
and suspend( ) do not release the lock as they are called.
You must be aware of this when working with locks. On the other hand, the
method wait( ) does release the lock when it is called,
which means that other synchronized methods in the thread object could
be called during a wait( ). In the following two classes, you’ll
see that the run( ) method is fully synchronized in both
cases, however, the Peeker still has full access to the synchronized
methods during a wait( ). This is because wait( )
releases the lock on the object as it suspends the method it’s called within.
You’ll
also see that there are two forms of wait( ). The first takes an
argument in milliseconds that has the same meaning as in sleep( ):
pause for this period of time. The difference is that in wait( ),
the object lock is released and you can come out of the wait( )
because of a notify( ) as well as having the clock run out.
The
second form takes no arguments, and means that the wait( ) will
continue until a notify( ) comes along and will not automatically
terminate after a time.
One
fairly unique aspect of wait( ) and notify( ) is that
both methods are part of the base class Object and not part of Thread
as are sleep( ), suspend( ), and resume( ).
Although this seems a bit strange at first—to have something that’s exclusively
for threading as part of the universal base class—it’s essential because they
manipulate the lock that’s also part of every object. As a result, you can put
a wait( ) inside any synchronized method, regardless of whether
there’s any threading going on inside that particular class. In fact, the only
place you can call wait( ) is within a synchronized method
or block. If you call wait( ) or notify( ) within a
method that’s not synchronized, the program will compile, but when you
run it you’ll get an IllegalMonitorStateException with the somewhat
nonintuitive message “current thread not owner.” Note that sleep( ),
suspend( ), and resume( ) can all be called within non-synchronized
methods since they don’t manipulate the lock.
You
can call wait( ) or notify( ) only for your own lock.
Again, you can compile code that tries to use the wrong lock, but it will
produce the same IllegalMonitorStateException message as before. You
can’t fool with someone else’s lock, but you can ask another object to perform
an operation that manipulates its own lock. So one approach is to create a synchronized
method that calls notify( ) for its own object. However, in Notifier
you’ll see the notify( ) call inside a synchronized block:
synchronized(wn2) {
wn2.notify();
}
where
wn2 is the object of type WaitNotify2. This method, which is not
part of WaitNotify2, acquires the lock on the wn2 object, at
which point it’s legal for it to call notify( ) for wn2 and
you won’t get the IllegalMonitorStateException.
///:Continuing
/////////// Blocking via wait() ///////////
class WaitNotify1 extends Blockable {
public WaitNotify1(Container c) { super(c); }
public synchronized void run() {
while(true) {
i++;
update();
try {
wait(1000);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class WaitNotify2 extends Blockable {
public WaitNotify2(Container c) {
super(c);
new Notifier(this);
}
public synchronized void run() {
while(true) {
i++;
update();
try {
wait();
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class Notifier extends Thread {
private WaitNotify2 wn2;
public Notifier(WaitNotify2 wn2) {
this.wn2 = wn2;
start();
}
public void run() {
while(true) {
try {
sleep(2000);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
synchronized(wn2) {
wn2.notify();
}
}
}
} ///:Continued
wait( ) is
typically used when you’ve gotten to the point where you’re waiting for some
other condition, under the control of forces outside your thread, to change and
you don’t want to idly wait by inside the thread. So wait( ) allows
you to put the thread to sleep while waiting for the world to change, and only
when a notify( ) or notifyAll( ) occurs does the thread
wake up and check for changes. Thus, it provides a way to synchronize between
threads.
If
a stream is waiting for some I/O activity, it will automatically block. In the
following portion of the example, the two classes work with generic Reader
and Writer objects, but in the test framework a piped stream will be set
up to allow the two threads to safely pass data to each other (which is the
purpose of piped streams).
The
Sender puts data into the Writer and sleeps for a random amount
of time. However, Receiver has no sleep( ), suspend( ),
or wait( ). But when it does a read( ) it automatically
blocks when there is no more data.
///:Continuing
class Sender extends Blockable { // send
private Writer out;
public Sender(Container c, Writer out) {
super(c);
this.out = out;
}
public void run() {
while(true) {
for(char c = 'A'; c <= 'z'; c++) {
try {
i++;
out.write(c);
state.setText("Sender sent: "
+ (char)c);
sleep((int)(3000 * Math.random()));
} catch(InterruptedException e) {
System.err.println("Interrupted");
} catch(IOException e) {
System.err.println("IO problem");
}
}
}
}
}
class Receiver extends Blockable {
private Reader in;
public Receiver(Container c, Reader in) {
super(c);
this.in = in;
}
public void run() {
try {
while(true) {
i++; // Show peeker it's alive
// Blocks until characters are there:
state.setText("Receiver read: "
+ (char)in.read());
}
} catch(IOException e) {
System.err.println("IO problem");
}
}
} ///:Continued
Both
classes also put information into their state fields and change i so
the Peeker can see that the thread is running.
The
main applet class is surprisingly simple because most of the work has been put
into the Blockable framework. Basically, an array of Blockable
objects is created, and since each one is a thread, they perform their own
activities when you press the “start” button. There’s also a button and actionPerformed( )
clause to stop all of the Peeker objects, which provides a demonstration
of the alternative to the deprecated (in Java 2) stop( ) method of Thread.
To
set up a connection between the Sender and Receiver objects, a PipedWriter
and PipedReader are created. Note that the PipedReader in must
be connected to the PipedWriter out via a constructor argument.
After that, anything that’s placed in out can later be extracted from in,
as if it passed through a pipe (hence the name). The in and out
objects are then passed to the Receiver and Sender constructors,
respectively, which treat them as Reader and Writer objects of
any type (that is, they are upcast).
The
array of Blockable references b is not initialized at its point
of definition because the piped streams cannot be set up before that definition
takes place (the need for the try block prevents this).
///:Continuing
/////////// Testing Everything ///////////
public class Blocking extends JApplet {
private JButton
start = new JButton("Start"),
stopPeekers = new JButton("Stop Peekers");
private boolean started = false;
private Blockable[] b;
private PipedWriter out;
private PipedReader in;
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(!started) {
started = true;
for(int i = 0; i < b.length; i++)
b[i].start();
}
}
}
class StopPeekersL implements ActionListener {
public void actionPerformed(ActionEvent e) {
// Demonstration of the preferred
// alternative to Thread.stop():
for(int i = 0; i < b.length; i++)
b[i].stopPeeker();
}
}
public void init() {
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
out = new PipedWriter();
try {
in = new PipedReader(out);
} catch(IOException e) {
System.err.println("PipedReader problem");
}
b = new Blockable[] {
new Sleeper1(cp),
new Sleeper2(cp),
new SuspendResume1(cp),
new SuspendResume2(cp),
new WaitNotify1(cp),
new WaitNotify2(cp),
new Sender(cp, out),
new Receiver(cp, in)
};
start.addActionListener(new StartL());
cp.add(start);
stopPeekers.addActionListener(
new StopPeekersL());
cp.add(stopPeekers);
}
public static void main(String[] args) {
Console.run(new Blocking(), 350, 550);
}
} ///:~
In
init( ), notice the loop that moves through the entire array and
adds the state and peeker.status text fields to the page.
When
the Blockable threads are initially created, each one automatically
creates and starts its own Peeker. So you’ll see the Peekers
running before the Blockable threads are started. This is important, as
some of the Peekers will get blocked and stop when the Blockable
threads start, and it’s essential to see this to understand that particular
aspect of blocking.
Because
threads can become blocked and because objects can have synchronized
methods that prevent threads from accessing that object until the
synchronization lock is released, it’s possible for one thread to get stuck
waiting for another thread, which in turn waits for another thread, etc., until
the chain leads back to a thread waiting on the first one. You get a continuous
loop of threads waiting on each other and no one can move. This is called deadlock.
The claim is that it doesn’t happen that often, but when it happens to you it’s
frustrating to debug.
There
is no language support to help prevent deadlock; it’s up to you to avoid it by
careful design. These are not comforting words to the person who’s trying to
debug a deadlocking program.
One
change that has been made in Java 2 to reduce the possibility of deadlock is
the deprecation of Thread’s stop( ), suspend( ),
resume( ), and destroy( ) methods.
The
reason that the stop( ) method is deprecated is because it doesn’t
release the locks that the thread has acquired, and if the objects are in an
inconsistent state (“damaged”) other threads can view and modify them in that
state. The resulting problems can be subtle and difficult to detect. Instead of
using stop( ), you should follow the example in Blocking.java
and use a flag to tell the thread when to terminate itself by exiting its run( )
method.
There
are times when a thread blocks—such as when it is waiting for input—and it
cannot poll a flag as it does in Blocking.java. In these cases, you
still shouldn’t use stop( ), but instead you can use the interrupt( )
method in Thread to break out of the blocked code:
//: c14:Interrupt.java
// The alternative approach to using
// stop() when a thread is blocked.
// <applet code=Interrupt width=200 height=100>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
class Blocked extends Thread {
public synchronized void run() {
try {
wait(); // Blocks
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
System.out.println("Exiting run()");
}
}
public class Interrupt extends JApplet {
private JButton
interrupt = new JButton("Interrupt");
private Blocked blocked = new Blocked();
public void init() {
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(interrupt);
interrupt.addActionListener(
new ActionListener() {
public
void actionPerformed(ActionEvent e) {
System.out.println("Button pressed");
if(blocked == null) return;
Thread remove = blocked;
blocked = null; // to release it
remove.interrupt();
}
});
blocked.start();
}
public static void main(String[] args) {
Console.run(new Interrupt(), 200, 100);
}
} ///:~
The
wait( ) inside Blocked.run( ) produces the blocked
thread. When you press the button, the blocked reference is set to null
so the garbage collector will clean it up, and then the object’s interrupt( )
method is called. The first time you press the button you’ll see that the
thread quits, but after that there’s no thread to kill so you just see that the
button has been pressed.
The
suspend( ) and resume( ) methods turn out to be
inherently deadlock-prone. When you call suspend( ), the target
thread stops but it still holds any locks that it has acquired up to that
point. So no other thread can access the locked resources until the thread is resumed.
Any thread that wants to resume the target thread and also tries to use any of
the locked resources produces deadlock. You should not use suspend( )
and resume( ), but instead put a flag in your Thread class
to indicate whether the thread should be active or suspended. If the flag
indicates that the thread is suspended, the thread goes into a wait using wait( ).
When the flag indicates that the thread should be resumed the thread is
restarted with notify( ). An example can be produced by modifying Counter2.java.
Although the effect is similar, you’ll notice that the code organization is
quite different—anonymous inner classes are used for all of the listeners and
the Thread is an inner class, which makes programming slightly more convenient
since it eliminates some of the extra bookkeeping necessary in Counter2.java:
//: c14:Suspend.java
// The alternative approach to using suspend()
// and resume(), which are deprecated in Java 2.
// <applet code=Suspend width=300 height=100>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
public class Suspend extends JApplet {
private JTextField t = new JTextField(10);
private JButton
suspend = new JButton("Suspend"),
resume = new JButton("Resume");
private Suspendable ss = new Suspendable();
class Suspendable extends Thread {
private int count = 0;
private boolean suspended = false;
public Suspendable() { start(); }
public void fauxSuspend() {
suspended = true;
}
public synchronized void fauxResume() {
suspended = false;
notify();
}
public void run() {
while (true) {
try {
sleep(100);
synchronized(this) {
while(suspended)
wait();
}
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
t.setText(Integer.toString(count++));
}
}
}
public void init() {
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(t);
suspend.addActionListener(
new ActionListener() {
public
void actionPerformed(ActionEvent e) {
ss.fauxSuspend();
}
});
cp.add(suspend);
resume.addActionListener(
new ActionListener() {
public
void actionPerformed(ActionEvent e) {
ss.fauxResume();
}
});
cp.add(resume);
}
public static void main(String[] args) {
Console.run(new Suspend(), 300, 100);
}
} ///:~
The
flag suspended inside Suspendable is used to turn suspension on
and off. To suspend, the flag is set to true by calling fauxSuspend( )
and this is detected inside run( ). The wait( ), as
described earlier in this chapter, must be synchronized so that it has
the object lock. In fauxResume( ), the suspended flag is set
to false and notify( ) is called—since this wakes up wait( )
inside a synchronized clause the fauxResume( ) method must
also be synchronized so that it acquires the lock before calling notify( )
(thus the lock is available for the wait( ) to wake up with). If
you follow the style shown in this program you can avoid using suspend( )
and resume( ).
The
destroy( ) method of Thread has never been implemented; it’s
like a suspend( ) that cannot resume, so it has the same deadlock
issues as suspend( ). However, this is not a deprecated method and
it might be implemented in a future version of Java (after 2) for special
situations in which the risk of a deadlock is acceptable.
You
might wonder why these methods, now deprecated, were included in Java in the
first place. It seems an admission of a rather significant mistake to simply
remove them outright (and pokes yet another hole in the arguments for Java’s
exceptional design and infallibility touted by Sun marketing people). The
heartening part about the change is that it clearly indicates that the
technical people and not the marketing people are running the show—they
discovered a problem and they are fixing it. I find this much more promising
and hopeful than leaving the problem in because “fixing it would admit an
error.” It means that Java will continue to improve, even if it means a little
discomfort on the part of Java programmers. I’d rather deal with the discomfort
than watch the language stagnate.
The
priority of a thread tells the scheduler how important this thread is.
If there are a number of threads blocked and waiting to be run, the scheduler
will run the one with the highest priority first. However, this doesn’t mean
that threads with lower priority don’t get run (that is, you can’t get
deadlocked because of priorities). Lower priority threads just tend to run less
often.
Although
priorities are interesting to know about and to play with, in practice you
almost never need to set priorities yourself. So feel free to skip the rest of
this section if priorities aren’t interesting to you.
You
can read the priority of a thread with getPriority( ) and change it
with setPriority( ). The form of the prior “counter” examples can
be used to show the effect of changing the priorities. In this applet you’ll
see that the counters slow down as the associated threads have their priorities
lowered:
//: c14:Counter5.java
// Adjusting the priorities of threads.
// <applet code=Counter5 width=450 height=600>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
class Ticker2 extends Thread {
private JButton
b = new JButton("Toggle"),
incPriority = new JButton("up"),
decPriority = new JButton("down");
private JTextField
t = new JTextField(10),
pr = new JTextField(3); // Display priority
private int count = 0;
private boolean runFlag = true;
public Ticker2(Container c) {
b.addActionListener(new ToggleL());
incPriority.addActionListener(new UpL());
decPriority.addActionListener(new DownL());
JPanel p = new JPanel();
p.add(t);
p.add(pr);
p.add(b);
p.add(incPriority);
p.add(decPriority);
c.add(p);
}
class ToggleL implements ActionListener {
public void actionPerformed(ActionEvent e) {
runFlag = !runFlag;
}
}
class UpL implements ActionListener {
public void actionPerformed(ActionEvent e) {
int newPriority = getPriority() + 1;
if(newPriority > Thread.MAX_PRIORITY)
newPriority = Thread.MAX_PRIORITY;
setPriority(newPriority);
}
}
class DownL implements ActionListener {
public void actionPerformed(ActionEvent e) {
int newPriority = getPriority() - 1;
if(newPriority < Thread.MIN_PRIORITY)
newPriority = Thread.MIN_PRIORITY;
setPriority(newPriority);
}
}
public void run() {
while (true) {
if(runFlag) {
t.setText(Integer.toString(count++));
pr.setText(
Integer.toString(getPriority()));
}
yield();
}
}
}
public class Counter5 extends JApplet {
private JButton
start = new JButton("Start"),
upMax = new JButton("Inc Max Priority"),
downMax = new JButton("Dec Max Priority");
private boolean started = false;
private static final int SIZE = 10;
private Ticker2[] s = new Ticker2[SIZE];
private JTextField mp = new JTextField(3);
public void init() {
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
for(int i = 0; i < s.length; i++)
s[i] = new Ticker2(cp);
cp.add(new JLabel(
"MAX_PRIORITY = " + Thread.MAX_PRIORITY));
cp.add(new JLabel("MIN_PRIORITY = "
+ Thread.MIN_PRIORITY));
cp.add(new JLabel("Group Max Priority = "));
cp.add(mp);
cp.add(start);
cp.add(upMax);
cp.add(downMax);
start.addActionListener(new StartL());
upMax.addActionListener(new UpMaxL());
downMax.addActionListener(new DownMaxL());
showMaxPriority();
// Recursively display parent thread groups:
ThreadGroup parent =
s[0].getThreadGroup().getParent();
while(parent != null) {
cp.add(new Label(
"Parent threadgroup max priority = "
+ parent.getMaxPriority()));
parent = parent.getParent();
}
}
public void showMaxPriority() {
mp.setText(Integer.toString(
s[0].getThreadGroup().getMaxPriority()));
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(!started) {
started = true;
for(int i = 0; i < s.length; i++)
s[i].start();
}
}
}
class UpMaxL implements ActionListener {
public void actionPerformed(ActionEvent e) {
int maxp =
s[0].getThreadGroup().getMaxPriority();
if(++maxp > Thread.MAX_PRIORITY)
maxp = Thread.MAX_PRIORITY;
s[0].getThreadGroup().setMaxPriority(maxp);
showMaxPriority();
}
}
class DownMaxL implements ActionListener {
public void actionPerformed(ActionEvent e) {
int maxp =
s[0].getThreadGroup().getMaxPriority();
if(--maxp < Thread.MIN_PRIORITY)
maxp = Thread.MIN_PRIORITY;
s[0].getThreadGroup().setMaxPriority(maxp);
showMaxPriority();
}
}
public static void main(String[] args) {
Console.run(new Counter5(), 450, 600);
}
} ///:~
Ticker2 follows
the form established earlier in this chapter, but there’s an extra
JTextField for displaying the priority of the thread and two more buttons
for incrementing and decrementing the priority.
Also
notice the use of yield( ), which voluntarily hands control back to
the scheduler. Without this the multithreading mechanism still works, but
you’ll notice it runs slowly (try removing the call to yield( ) to
see this). You could also call sleep( ), but then the rate of
counting would be controlled by the sleep( ) duration instead of
the priority.
The
init( ) in Counter5 creates an array of ten Ticker2s;
their buttons and fields are placed on the form by the Ticker2
constructor. Counter5 adds buttons to start everything up as well as
increment and decrement the maximum priority of the thread group. In addition,
there are labels that display the maximum and minimum priorities possible for a
thread and a JTextField to show the thread group’s maximum priority.
(The next section will describe thread groups.) Finally, the priorities of the
parent thread groups are also displayed as labels.
When
you press an “up” or “down” button, that Ticker2’s priority is fetched
and incremented or decremented accordingly.
When
you run this program, you’ll notice several things. First of all, the thread
group’s default priority is five. Even if you decrement the maximum priority
below five before starting the threads (or before creating the threads, which
requires a code change), each thread will have a default priority of five.
The
simple test is to take one counter and decrement its priority to one, and
observe that it counts much slower. But now try to increment it again. You can
get it back up to the thread group’s priority, but no higher. Now decrement the
thread group’s priority a couple of times. The thread priorities are unchanged,
but if you try to modify them either up or down you’ll see that they’ll
automatically pop to the priority of the thread group. Also, new threads will
still be given a default priority, even if that’s higher than the group
priority. (Thus the group priority is not a way to prevent new threads from
having higher priorities than existing ones.)
Finally,
try to increment the group maximum priority. It can’t be done. You can only
reduce thread group maximum priorities, not increase them.
All
threads belong to a thread group. This can be either the default thread group
or a group you explicitly specify when you create the thread. At creation, the
thread is bound to a group and cannot change to a different group. Each
application has at least one thread that belongs to the system thread group. If
you create more threads without specifying a group, they will also belong to
the system thread group.
Thread
groups must also belong to other thread groups. The thread group that a new one
belongs to must be specified in the constructor. If you create a thread group
without specifying a thread group for it to belong to, it will be placed under
the system thread group. Thus, all thread groups in your application will
ultimately have the system thread group as the parent.
The
reason for the existence of thread groups is hard to determine from the
literature, which tends to be confusing on this subject. It’s often cited as
“security reasons.” According to Arnold & Gosling,[71]
“Threads within a thread group can modify the other threads in the group,
including any farther down the hierarchy. A thread cannot modify threads
outside of its own group or contained groups.” It’s hard to know what “modify”
is supposed to mean here. The following example shows a thread in a “leaf”
subgroup modifying the priorities of all the threads in its tree of thread
groups as well as calling a method for all the threads in its tree.
//: c14:TestAccess.java
// How threads can access other threads
// in a parent thread group.
public class TestAccess {
public static void main(String[] args) {
ThreadGroup
x = new ThreadGroup("x"),
y = new ThreadGroup(x, "y"),
z = new ThreadGroup(y, "z");
Thread
one = new TestThread1(x, "one"),
two = new TestThread2(z, "two");
}
}
class TestThread1 extends Thread {
private int i;
TestThread1(ThreadGroup g, String name) {
super(g, name);
}
void f() {
i++; // modify this thread
System.out.println(getName() + " f()");
}
}
class TestThread2 extends TestThread1 {
TestThread2(ThreadGroup g, String name) {
super(g, name);
start();
}
public void run() {
ThreadGroup g =
getThreadGroup().getParent().getParent();
g.list();
Thread[] gAll = new Thread[g.activeCount()];
g.enumerate(gAll);
for(int i = 0; i < gAll.length; i++) {
gAll[i].setPriority(Thread.MIN_PRIORITY);
((TestThread1)gAll[i]).f();
}
g.list();
}
} ///:~
In
main( ), several ThreadGroups are created, leafing off from
each other: x has no argument but its name (a String), so it is
automatically placed in the “system” thread group, while y is under x
and z is under y. Note that initialization happens in textual
order so this code is legal.
Two
threads are created and placed in different thread groups. TestThread1
doesn’t have a run( ) method but it does have an f( )
that modifies the thread and prints something so you can see it was called. TestThread2
is a subclass of TestThread1 and its run( ) is fairly
elaborate. It first gets the thread group of the current thread, then moves up
the heritage tree by two levels using getParent( ). (This is
contrived since I purposely place the TestThread2 object two levels down
in the hierarchy.) At this point, an array of references to Threads is
created using the method activeCount( ) to ask how many threads are
in this thread group and all the child thread groups. The enumerate( )
method places references to all of these threads in the array gAll, then
I simply move through the entire array calling the f( ) method for
each thread, as well as modifying the priority. Thus, a thread in a “leaf”
thread group modifies threads in parent thread groups.
The
debugging method list( ) prints all the information about a thread
group to standard output and is helpful when investigating thread group
behavior. Here’s the output of the program:
java.lang.ThreadGroup[name=x,maxpri=10]
Thread[one,5,x]
java.lang.ThreadGroup[name=y,maxpri=10]
java.lang.ThreadGroup[name=z,maxpri=10]
Thread[two,5,z]
one f()
two f()
java.lang.ThreadGroup[name=x,maxpri=10]
Thread[one,1,x]
java.lang.ThreadGroup[name=y,maxpri=10]
java.lang.ThreadGroup[name=z,maxpri=10]
Thread[two,1,z]
Not
only does list( ) print the class name of ThreadGroup or Thread,
but it also prints the thread group name and its maximum priority. For threads,
the thread name is printed, followed by the thread priority and the group that
it belongs to. Note that list( ) indents the threads and thread
groups to indicate that they are children of the unindented thread group.
You
can see that f( ) is called by the TestThread2 run( )
method, so it’s obvious that all threads in a group are vulnerable. However,
you can access only the threads that branch off from your own system
thread group tree, and perhaps this is what is meant by “safety.” You cannot
access anyone else’s system thread group tree.
Putting
aside the safety issue, one thing thread groups seem to be useful for is
control: you can perform certain operations on an entire thread group with a
single command. The following example demonstrates this, and the restrictions
on priorities within thread groups. The commented numbers in parentheses
provide a reference to compare to the output.
//: c14:ThreadGroup1.java
// How thread groups control priorities
// of the threads inside them.
public class ThreadGroup1 {
public static void main(String[] args) {
// Get the system thread & print its Info:
ThreadGroup sys =
Thread.currentThread().getThreadGroup();
sys.list(); // (1)
// Reduce the system thread group priority:
sys.setMaxPriority(Thread.MAX_PRIORITY - 1);
// Increase the main thread priority:
Thread curr = Thread.currentThread();
curr.setPriority(curr.getPriority() + 1);
sys.list(); // (2)
// Attempt to set a new group to the max:
ThreadGroup g1 = new ThreadGroup("g1");
g1.setMaxPriority(Thread.MAX_PRIORITY);
// Attempt to set a new thread to the max:
Thread t = new Thread(g1, "A");
t.setPriority(Thread.MAX_PRIORITY);
g1.list(); // (3)
// Reduce g1's max priority, then attempt
// to increase it:
g1.setMaxPriority(Thread.MAX_PRIORITY - 2);
g1.setMaxPriority(Thread.MAX_PRIORITY);
g1.list(); // (4)
// Attempt to set a new thread to the max:
t = new Thread(g1, "B");
t.setPriority(Thread.MAX_PRIORITY);
g1.list(); // (5)
// Lower the max priority below the default
// thread priority:
g1.setMaxPriority(Thread.MIN_PRIORITY + 2);
// Look at a new thread's priority before
// and after changing it:
t = new Thread(g1, "C");
g1.list(); // (6)
t.setPriority(t.getPriority() -1);
g1.list(); // (7)
// Make g2 a child Threadgroup of g1 and
// try to increase its priority:
ThreadGroup g2 = new ThreadGroup(g1, "g2");
g2.list(); // (8)
g2.setMaxPriority(Thread.MAX_PRIORITY);
g2.list(); // (9)
// Add a bunch of new threads to g2:
for (int i = 0; i < 5; i++)
new Thread(g2, Integer.toString(i));
// Show information about all threadgroups
// and threads:
sys.list(); // (10)
System.out.println("Starting all threads:");
Thread[] all = new Thread[sys.activeCount()];
sys.enumerate(all);
for(int i = 0; i < all.length; i++)
if(!all[i].isAlive())
all[i].start();
// Suspends & Stops all threads in
// this group and its subgroups:
System.out.println("All threads started");
sys.suspend(); // Deprecated in Java 2
// Never gets here...
System.out.println("All threads suspended");
sys.stop(); // Deprecated in Java 2
System.out.println("All threads stopped");
}
} ///:~
The
output that follows has been edited to allow it to fit on the page (the java.lang.
has been removed) and to add numbers to correspond to the commented numbers in
the listing above.
(1) ThreadGroup[name=system,maxpri=10]
Thread[main,5,system]
(2) ThreadGroup[name=system,maxpri=9]
Thread[main,6,system]
(3) ThreadGroup[name=g1,maxpri=9]
Thread[A,9,g1]
(4) ThreadGroup[name=g1,maxpri=8]
Thread[A,9,g1]
(5) ThreadGroup[name=g1,maxpri=8]
Thread[A,9,g1]
Thread[B,8,g1]
(6) ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,6,g1]
(7) ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,3,g1]
(8) ThreadGroup[name=g2,maxpri=3]
(9) ThreadGroup[name=g2,maxpri=3]
(10)ThreadGroup[name=system,maxpri=9]
Thread[main,6,system]
ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,3,g1]
ThreadGroup[name=g2,maxpri=3]
Thread[0,6,g2]
Thread[1,6,g2]
Thread[2,6,g2]
Thread[3,6,g2]
Thread[4,6,g2]
Starting all threads:
All threads started
All
programs have at least one thread running, and the first action in main( )
is to call the static method of Thread called currentThread( ).
From this thread, the thread group is produced and list( ) is called
for the result. The output is:
(1) ThreadGroup[name=system,maxpri=10]
Thread[main,5,system]
You
can see that the name of the main thread group is system, and the name
of the main thread is main, and it belongs to the system thread
group.
The
second exercise shows that the system group’s maximum priority can be
reduced and the main thread can have its priority increased:
(2) ThreadGroup[name=system,maxpri=9]
Thread[main,6,system]
The
third exercise creates a new thread group, g1, which automatically
belongs to the system thread group since it isn’t otherwise specified. A
new thread A is placed in g1. After attempting to set this
group’s maximum priority to the highest level and A’s priority to the
highest level, the result is:
(3) ThreadGroup[name=g1,maxpri=9]
Thread[A,9,g1]
Thus,
it’s not possible to change the thread group’s maximum priority to be higher
than its parent thread group.
The
fourth exercise reduces g1’s maximum priority by two and then tries to
increase it up to Thread.MAX_PRIORITY. The result is:
(4) ThreadGroup[name=g1,maxpri=8]
Thread[A,9,g1]
You
can see that the increase in maximum priority didn’t work. You can only
decrease a thread group’s maximum priority, not increase it. Also, notice that
thread A’s priority didn’t change, and now it is higher than the thread
group’s maximum priority. Changing a thread group’s maximum priority doesn’t
affect existing threads.
The
fifth exercise attempts to set a new thread to maximum priority:
(5) ThreadGroup[name=g1,maxpri=8]
Thread[A,9,g1]
Thread[B,8,g1]
The
new thread cannot be changed to anything higher than the maximum thread group
priority.
The
default thread priority for this program is six; that’s the priority a new
thread will be created at and where it will stay if you don’t manipulate the
priority. Exercise 6 lowers the maximum thread group priority below the default
thread priority to see what happens when you create a new thread under this
condition:
(6) ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,6,g1]
Even
though the maximum priority of the thread group is three, the new thread is
still created using the default priority of six. Thus, maximum thread group
priority does not affect default priority. (In fact, there appears to be no way
to set the default priority for new threads.)
After
changing the priority, attempting to decrement it by one, the result is:
(7) ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,3,g1]
Only
when you attempt to change the priority is the thread group’s maximum priority
enforced.
A
similar experiment is performed in (8) and (9), in which a new thread group g2
is created as a child of g1 and its maximum priority is changed. You
can see that it’s impossible for g2’s maximum to go higher than g1’s:
(8) ThreadGroup[name=g2,maxpri=3]
(9) ThreadGroup[name=g2,maxpri=3]
Also
notice that g2 is automatically set to the thread group maximum priority
of g1 as g2 is created.
After
all of these experiments, the entire system of thread groups and threads is
printed:
(10)ThreadGroup[name=system,maxpri=9]
Thread[main,6,system]
ThreadGroup[name=g1,maxpri=3]
Thread[A,9,g1]
Thread[B,8,g1]
Thread[C,3,g1]
ThreadGroup[name=g2,maxpri=3]
Thread[0,6,g2]
Thread[1,6,g2]
Thread[2,6,g2]
Thread[3,6,g2]
Thread[4,6,g2]
So
because of the rules of thread groups, a child group must always have a maximum
priority that’s less than or equal to its parent’s maximum priority.
The
last part of this program demonstrates methods for an entire group of threads.
First the program moves through the entire tree of threads and starts each one
that hasn’t been started. For drama, the system group is then suspended
and finally stopped. (Although it’s interesting to see that suspend( )
and stop( ) work on entire thread groups, you should keep in mind
that these methods are deprecated in Java 2.) But when you suspend the system
group you also suspend the main thread and the whole program shuts down,
so it never gets to the point where the threads are stopped. Actually, if you
do stop the main thread it throws a ThreadDeath exception, so this
is not a typical thing to do. Since ThreadGroup is inherited from Object,
which contains the wait( ) method, you can also choose to suspend
the program for any number of seconds by calling wait(seconds * 1000).
This must acquire the lock inside a synchronized block, of course.
The
ThreadGroup class also has suspend( ) and resume( )
methods so you can stop and start an entire thread group and all of its threads
and subgroups with a single command. (Again, suspend( ) and resume( )
are deprecated in Java 2.)
Thread
groups can seem a bit mysterious at first, but keep in mind that you probably
won’t be using them directly very often.
Earlier
in this chapter, I suggested that you think carefully before making an applet
or main Frame as an implementation of Runnable. Of course, if you
must inherit from a class and you want to add threading behavior to the
class, Runnable is the correct solution. The final example in this
chapter exploits this by making a Runnable JPanel class that
paints different colors on itself. This application is set up to take values
from the command line to determine how big the grid of colors is and how long
to sleep( ) between color changes. By playing with these values
you’ll discover some interesting and possibly inexplicable features of threads:
//: c14:ColorBoxes.java
// Using the Runnable interface.
// <applet code=ColorBoxes width=500 height=400>
// <param name=grid value="12">
// <param name=pause value="50">
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
class CBox extends JPanel implements Runnable {
private Thread t;
private int pause;
private static final Color[] colors = {
Color.black, Color.blue, Color.cyan,
Color.darkGray, Color.gray, Color.green,
Color.lightGray, Color.magenta,
Color.orange, Color.pink, Color.red,
Color.white, Color.yellow
};
private Color cColor = newColor();
private static final Color newColor() {
return colors[
(int)(Math.random() * colors.length)
];
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(cColor);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
}
public CBox(int pause) {
this.pause = pause;
t = new Thread(this);
t.start();
}
public void run() {
while(true) {
cColor = newColor();
repaint();
try {
t.sleep(pause);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
public class ColorBoxes extends JApplet {
private boolean isApplet = true;
private int grid = 12;
private int pause = 50;
public void init() {
// Get parameters from Web page:
if (isApplet) {
String gsize = getParameter("grid");
if(gsize != null)
grid = Integer.parseInt(gsize);
String pse = getParameter("pause");
if(pse != null)
pause = Integer.parseInt(pse);
}
Container cp = getContentPane();
cp.setLayout(new GridLayout(grid, grid));
for (int i = 0; i < grid * grid; i++)
cp.add(new CBox(pause));
}
public static void main(String[] args) {
ColorBoxes applet = new ColorBoxes();
applet.isApplet = false;
if(args.length > 0)
applet.grid = Integer.parseInt(args[0]);
if(args.length > 1)
applet.pause = Integer.parseInt(args[1]);
Console.run(applet, 500, 400);
}
} ///:~
ColorBoxes is the
usual applet/application with an init( ) that sets up the GUI. This
sets up the GridLayout so that it has grid cells in each
dimension. Then it adds the appropriate number of CBox objects to fill
the grid, passing the pause value to each one. In main( )
you can see how pause and grid have default values that can be
changed if you pass in command-line arguments, or by using applet parameters.
CBox
is where all the work takes place. This is inherited from JPanel
and it implements the Runnable interface so each JPanel can also
be a Thread. Remember that when you implement Runnable, you don’t
make a Thread object, just a class that has a run( ) method.
Thus, you must explicitly create a Thread object and hand the Runnable
object to the constructor, then call start( ) (this happens in the
constructor). In CBox this thread is called t.
Notice
the array colors, which is an enumeration of all the colors in class Color.
This is used in newColor( ) to produce a randomly selected color.
The current cell color is cColor.
paintComponent( ) is
quite simple—it just sets the color to cColor and fills the entire JPanel
with that color.
In
run( ), you see the infinite loop that sets the cColor to a
new random color and then calls repaint( ) to show it. Then the
thread goes to sleep( ) for the amount of time specified on the
command line.
Precisely
because this design is flexible and threading is tied to each JPanel
element, you can experiment by making as many threads as you want. (In reality,
there is a restriction imposed by the number of threads your JVM can
comfortably handle.)
This
program also makes an interesting benchmark, since it can show dramatic
performance differences between one JVM threading implementation and another.
At
some point, you’ll find that ColorBoxes bogs down. On my machine, this
occurred somewhere after a 10 x 10 grid. Why does this happen? You’re naturally
suspicious that Swing might have something to do with it, so here’s an example
that tests that premise by making fewer threads. The following code is
reorganized so that an ArrayList implements Runnable and that ArrayList
holds a number of color blocks and randomly chooses ones to update. Then a
number of these ArrayList objects are created, depending roughly on the
grid dimension you choose. As a result, you have far fewer threads than color
blocks, so if there’s a speedup we’ll know it was because there were too many
threads in the previous example:
//: c14:ColorBoxes2.java
// Balancing thread use.
// <applet code=ColorBoxes2 width=600 height=500>
// <param name=grid value="12">
// <param name=pause value="50">
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;
class CBox2 extends JPanel {
private static final Color[] colors = {
Color.black, Color.blue, Color.cyan,
Color.darkGray, Color.gray, Color.green,
Color.lightGray, Color.magenta,
Color.orange, Color.pink, Color.red,
Color.white, Color.yellow
};
private Color cColor = newColor();
private static final Color newColor() {
return colors[
(int)(Math.random() * colors.length)
];
}
void nextColor() {
cColor = newColor();
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(cColor);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
}
}
class CBoxList
extends ArrayList implements Runnable {
private Thread t;
private int pause;
public CBoxList(int pause) {
this.pause = pause;
t = new Thread(this);
}
public void go() { t.start(); }
public void run() {
while(true) {
int i = (int)(Math.random() * size());
((CBox2)get(i)).nextColor();
try {
t.sleep(pause);
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
public Object last() { return get(size() - 1);}
}
public class ColorBoxes2 extends JApplet {
private boolean isApplet = true;
private int grid = 12;
// Shorter default pause than ColorBoxes:
private int pause = 50;
private CBoxList[] v;
public void init() {
// Get parameters from Web page:
if (isApplet) {
String gsize = getParameter("grid");
if(gsize != null)
grid = Integer.parseInt(gsize);
String pse = getParameter("pause");
if(pse != null)
pause = Integer.parseInt(pse);
}
Container cp = getContentPane();
cp.setLayout(new GridLayout(grid, grid));
v = new CBoxList[grid];
for(int i = 0; i < grid; i++)
v[i] = new CBoxList(pause);
for (int i = 0; i < grid * grid; i++) {
v[i % grid].add(new CBox2());
cp.add((CBox2)v[i % grid].last());
}
for(int i = 0; i < grid; i++)
v[i].go();
}
public static void main(String[] args) {
ColorBoxes2 applet = new ColorBoxes2();
applet.isApplet = false;
if(args.length > 0)
applet.grid = Integer.parseInt(args[0]);
if(args.length > 1)
applet.pause = Integer.parseInt(args[1]);
Console.run(applet, 500, 400);
}
} ///:~
In
ColorBoxes2 an array of CBoxList is created and initialized to
hold grid CBoxLists, each of which knows how long to sleep. An
equal number of CBox2 objects is then added to each CBoxList, and
each list is told to go( ), which starts its thread.
CBox2 is
similar to CBox: it paints itself with a randomly chosen color. But
that’s all a CBox2 does. All of the threading has been moved into
CBoxList.
The
CBoxList could also have inherited Thread and had a member object
of type ArrayList. That design has the advantage that the add( )
and get( ) methods could then be given specific argument and return
value types instead of generic Objects. (Their names could also be
changed to something shorter.) However, the design used here seemed at first
glance to require less code. In addition, it automatically retains all the
other behaviors of an ArrayList. With all the casting and parentheses
necessary for get( ), this might not be the case as your body of
code grows.
As
before, when you implement Runnable you don’t get all of the equipment
that comes with Thread, so you have to create a new Thread and
hand yourself to its constructor in order to have something to start( ),
as you can see in the CBoxList constructor and in go( ). The
run( ) method simply chooses a random element number within the
list and calls nextColor( ) for that element to cause it to choose
a new randomly selected color.
Upon
running this program, you see that it does indeed run faster and respond more
quickly (for instance, when you interrupt it, it stops more quickly), and it
doesn’t seem to bog down as much at higher grid sizes. Thus, a new factor is
added into the threading equation: you must watch to see that you don’t have
“too many threads” (whatever that turns out to mean for your particular program
and platform—here, the slowdown in ColorBoxes appears to be caused by
the fact that there’s only one thread that is responsible for all painting, and
it gets bogged down by too many requests). If you have too many threads, you
must try to use techniques like the one above to “balance” the number of
threads in your program. If you see performance problems in a multithreaded
program you now have a number of issues to examine:
1.
Do you have enough calls to sleep( ), yield( ),
and/or wait( )?
2.
Are calls to sleep( ) long enough?
3.
Are you running too many threads?
4.
Have you tried different platforms and JVMs?
Issues
like this are one reason that multithreaded programming is often considered an
art.
It
is vital to learn when to use multithreading and when to avoid it. The main
reason to use it is to manage a number of tasks whose intermingling will make
more efficient use of the computer (including the ability to transparently
distribute the tasks across multiple CPUs) or be more convenient for the user.
The classic example of resource balancing is using the CPU during I/O waits.
The classic example of user convenience is monitoring a “stop” button during
long downloads.
The
main drawbacks to multithreading are:
1.
Slowdown while waiting for shared resources
2.
Additional CPU overhead required to manage threads
3.
Unrewarded complexity, such as the silly idea of having a
separate thread to update each element of an array
4.
Pathologies including starving, racing, and deadlock
An
additional advantage to threads is that they substitute “light” execution
context switches (of the order of 100 instructions) for “heavy” process context
switches (of the order of 1000s of instructions). Since all threads in a given
process share the same memory space, a light context switch changes only
program execution and local variables. On the other hand, a process change—the
heavy context switch—must exchange the full memory space.
Threading
is like stepping into an entirely new world and learning a whole new
programming language, or at least a new set of language concepts. With the
appearance of thread support in most microcomputer operating systems,
extensions for threads have also been appearing in programming languages or
libraries. In all cases, thread programming (1) seems mysterious and requires a
shift in the way you think about programming; and (2) looks similar to thread
support in other languages, so when you understand threads, you understand a
common tongue. And although support for threads can make Java seem like a more
complicated language, don’t blame Java. Threads are tricky.
One
of the biggest difficulties with threads occurs because more than one thread
might be sharing a resource—such as the memory in an object—and you must make
sure that multiple threads don’t try to read and change that resource at the
same time. This requires judicious use of the synchronized keyword,
which is a helpful tool but must be understood thoroughly because it can
quietly introduce deadlock situations.
In
addition, there’s a certain art to the application of threads. Java is designed
to allow you to create as many objects as you need to solve your problem—at
least in theory. (Creating millions of objects for an engineering
finite-element analysis, for example, might not be practical in Java.) However,
it seems that there is an upper bound to the number of threads you’ll want to
create, because at some point a large number of threads seems to become
unwieldy. This critical point is not in the many thousands as it might be with
objects, but rather in the low hundreds, sometimes less than 100. As you often
create only a handful of threads to solve a problem, this is typically not much
of a limit, yet in a more general design it becomes a constraint.
A
significant nonintuitive issue in threading is that, because of thread
scheduling, you can typically make your applications run faster by
inserting calls to sleep( ) inside run( )’s main loop.
This definitely makes it feel like an art, in particular when the longer delays
seem to speed up performance. Of course, the reason this happens is that
shorter delays can cause the end-of-sleep( ) scheduler interrupt to
happen before the running thread is ready to go to sleep, forcing the scheduler
to stop it and restart it later so it can finish what it was doing and then go
to sleep. It takes extra thought to realize how messy things can get.
One
thing you might notice missing in this chapter is an animation example, which
is one of the most popular things to do with applets. However, a complete
solution (with sound) to this problem comes with the Java JDK (available at java.sun.com)
in the demo section. In addition, we can expect better animation support to
become part of future versions of Java, while completely different non-Java,
non-programming solutions to animation for the Web are appearing that will
probably be superior to traditional approaches. For explanations about how Java
animation works, see Core Java 2 by Horstmann & Cornell,
Prentice-Hall, 1997. For more advanced discussions of threading, see Concurrent
Programming in Java by Doug Lea, Addison-Wesley, 1997, or Java Threads
by Oaks & Wong, O’Reilly, 1997.
Solutions
to selected exercises can be found in the electronic document The Thinking
in Java Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
1.
Inherit a class from Thread and override the run( )
method. Inside run( ), print a message, and then call sleep( ).
Repeat this three times, then return from run( ). Put a start-up
message in the constructor and override finalize( ) to print a
shut-down message. Make a separate thread class that calls System.gc( )
and System.runFinalization( ) inside run( ), printing a
message as it does so. Make several thread objects of both types and run them
to see what happens.
2.
Modify Sharing2.java to add a synchronized
block inside the run( ) method of TwoCounter instead of
synchronizing the entire run( ) method.
3.
Create two Thread subclasses, one with a run( )
that starts up, captures the reference of the second Thread object and
then calls wait( ). The other class’ run( ) should call
notifyAll( ) for the first thread after some number of seconds have
passed, so the first thread can print a message.
4.
In Counter5.java inside Ticker2, remove the yield( )
and explain the results. Replace the yield( ) with a sleep( )
and explain the results.
5.
In ThreadGroup1.java, replace the call to sys.suspend( )
with a call to wait( ) for the thread group, causing it to wait for
two seconds. For this to work correctly you must acquire the lock for sys inside
a synchronized block.
6.
Change Daemons.java so that main( ) has
a sleep( ) instead of a readLine( ). Experiment with
different sleep times to see what happens.
7.
In Chapter 8, locate the GreenhouseControls.java
example, which consists of three files. In Event.java, the class Event
is based on watching the time. Change Event so that it is a Thread,
and change the rest of the design so that it works with this new Thread-based
Event.
8.
Modify Exercise 7 so that the java.util.Timer class
found in JDK 1.3 is used to run the system.
9.
Starting with SineWave.java from Chapter 13, create
a program (an applet/application using the Console class) that draws an
animated sine wave that appears to scrolls past the viewing window like an
oscilloscope, driving the animation with a Thread. The speed of the
animation should be controlled with a java.swing.JSlider control.
10.
Modify Exercise 9 so that multiple sine wave panels are
created within the application. The number of sine wave panels should be
controlled by HTML tags or command-line parameters.
11.
Modify Exercise 9 so that the java.swing.Timer
class is used to drive the animation. Note the difference between this and java.util.Timer.