By Scott Oaks, Henry Wong
Book Price: $39.95 USD
£28.50 GBP
PDF Price: $31.99
Cover | Table of Contents | Colophon
http://www.oreilly.com/catalog/jthreads3.package javathreads.examples.ch02). The remaining
classes are in an example-specific package (e.g., package
javathreads.examples.ch02.example1). Package names are
shown within the text for all classes.package javathreads.examples.ch02.example2;
...
public class SwingTypeTester extends JFrame {
...
private JButton stopButton;
...
private void initComponents( ) {
...
stopButton = new JButton( );stopButton) and some
new code added to the initComponents() method.-source argument:piccolo% java -source 1.5 javathreads/examples/ch02/example1/*.java
-source argument is not needed for a
great many of our examples, we always use it for consistency.piccolo% java javathreads.examples.ch02.example1.SwingTypeTester
package javathreads.examples.ch02.example1;
public class Factorial {
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
System.out.print(n + "! is ");
int fact = 1;
while (n > 1)
fact *= n--;
System.out.println(fact);
}
}args[0] to an integer.1 in a location called
fact.1.fact by the
value stored in n and decrement
n by 1.fact.package javathreads.examples.ch02.example1;
public class Factorial {
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
System.out.print(n + "! is ");
int fact = 1;
while (n > 1)
fact *= n--;
System.out.println(fact);
}
}args[0] to an integer.1 in a location called
fact.1.fact by the
value stored in n and decrement
n by 1.fact.main()
method of your class. In other programming
languages, the starting point may be different, and the terminology
may be different, but the basic idea is the same.Thread class and using the
Runnable interface.
The
Runnable interface (generally) requires an
instance of a thread, so we begin with the Thread
class.package javathreads.examples.ch02;
public interface CharacterSource {
public void addCharacterListener(CharacterListener cl);
public void removeCharacterListener(CharacterListener cl);
public void nextCharacter( );
}package javathreads.examples.ch02;
public interface CharacterListener {
public void newCharacter(CharacterEvent ce);
}package javathreads.examples.ch02;
public class CharacterEvent {
public CharacterSource source;
public int character;
public CharacterEvent(CharacterSource cs, int c) {
source = cs;
character = c;
}
}Thread class that affect the
thread's lifecycle are:package java.lang;
public class Thread implements Runnable {
public void start( );
public void run( );
public void stop( ); // Deprecated, do not use
public void resume( ); // Deprecated, do not use
public void suspend( ); // Deprecated, do not use
public static void sleep(long millis);
public static void sleep(long millis, int nanos);
public boolean isAlive( );
public void interrupt( );
public boolean isInterrupted( );
public static boolean interrupted( );
public void join( ) throws InterruptedException;
}
Thread class, so creating a thread is done by
calling a constructor of that class. In our example, we use the
simplest constructor available to us. Additional constructors of the
Thread class allow you to specify the
thread's name or a Runnable
object to serve as the thread's target.Runnable interface later in this
chapter.RandomCharacterGenerator thread
to follow this approach:package javathreads.examples.ch02.example3;
...
public class RandomCharacterGenerator extends Thread implements CharacterSource {
...
private volatile boolean done = false;
...
public void run( ) {
while (!done) {
...
}
}
public void setDone( ) {
done = true;
}
}done to signal the thread that it should quit. Now
instead of looping forever, the run() method
examines the state of that variable on every loop and returns when
the done flag has been set. That terminates the
thread.
package javathreads.examples.ch02.example3;
...
public class SwingTypeTester extends JFrame implements CharacterSource {
...
private JButton stopButton;
...
private void initComponents( ) {
...
stopButton = new JButton( );
stopButton.setLabel("Stop");
p.add(stopButton);
...
stopButton.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent evt) {
startButton.setEnabled(true);
stopButton.setEnabled(false);
producer.setDone( );
feedbackCanvas.setEnabled(false);
}
});
...
}
...
}setDone()
method is called, and the next time the
RandomCharacterGenerator thread executes the top
of its loop, that thread exits. This process also reenables the Start
button: we can start a new thread at any time.Runnable interface
(java.lang.Runnable). The
Thread class implements this interface, which
contains a single method:package java.lang;
public interface Runnable {
public void run( );
}Runnable interface allows you to separate the
implementation of a task from the thread used to run the task. For
example, instead of extending the Thread class,
our RandomCharacterGenerator class might have
implemented the Runnable interface:package javathreads.examples.ch02.example5;
...
// Note: Use Example 3 as the basis for comparison
public class RandomCharacterGenerator implements Runnable {
...
}RandomCharacterGenerator object must be
constructed:package javathreads.examples.ch02.example5;
...
public class SwingTypeTester extends JFrame implements CharacterSource {
...
private void initComponents( ) {
...
startButton.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent evt) {
producer = new RandomCharacterGenerator( );
displayCanvas.setCharacterSource(producer);
Thread t = new Thread(producer);
t.start( );
startButton.setEnabled(false);
stopButton.setEnabled(true);
feedbackCanvas.setEnabled(true);
feedbackCanvas.requestFocus( );
}
});
...
}
...
}producer) to the thread's
constructor. Then we start the thread (instead of starting the
runnable object).Runnable interface or the
Thread class when designing your own application.
The answer is yes.Runnable interface and sometimes it makes sense to
use the Thread class. The answer depends on
whether you would like your new class to inherit behavior from the
Thread class or if your class needs to inherit
from other classes.RandomCharacterGenerator thread. We saw how
another class (the SwingTypeTester class) kept a
reference to that thread and how it continued to call methods on that
object.RandomCharacterGenerator class, they are not
executed by that thread. Instead, methods like the setDone(
) method are executed by the Swing
event-dispatching
thread as it executes the actionPerformed()
method within the SwingTypeTester class. As far as
the virtual machine is concerned, the setDone()
method is just a series of statements; those statements do not
"belong" to any particular thread.
Therefore, the event-dispatching thread executes the
setDone() method in exactly the same way in which
it executes any other method.Thread class is just an object: it may be passed
to other methods, and any thread that has a reference to another
thread can execute any method of that other thread's
Thread object. The Thread
object is not the thread itself; it is instead a set of methods and
data that encapsulates information about the thread. And that method
and data can be accessed by any other thread.AnimatedCharacterCanvas class and determine how
many threads execute some of its methods. You should be comfortable
with the fact that four different threads use
this object. The RandomCharacterGenerator thread
invokes the newChar() method on that object. The
timing thread invokes the run() method. The
setDone() method is invoked by the Swing
event-dispatching thread. And the constructor of the class (i.e., the
default constructor) is invoked by the main method of the application
as it constructs the GUI.Runnable interface and how
that interface allows us a great degree of flexibility in how we
develop the class hierarchy for our objects. Tasks can be either
Thread objects or Runnable
objects associated with a thread. Using the
Runnable interface allows more flexibility in how
you define your tasks, but both approaches have merit in different
situations.|
Description
|
Main Java class
|
Ant target
|
|---|---|---|
|
Factorial Example
|
javathreads.examples.ch02.example1.Factorial number
|
ch2-ex1
|
|
First Swing Type Tester
|
javathreads.examples.ch02.example2.SwingTypeTester |
done flag). In
others, we needed to change a character variable that was used in the
animation canvas; this was done by a thread different than the Swing
thread that redraws the canvas. We glossed over the details at the
time, which may have given the implication that they are minor
issues. However, we must understand that when two threads share data,
complexities arise. These complexities must be taken into
consideration whether we're implementing a large
shared database or simply sharing a done flag.AnimatedDisplayCanvas class
from the previous chapter:package javathreads.examples.ch02.example7;
private volatile boolean done = false;
private int curX = 0;
public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas
implements CharacterListener, Runnable {
...
public synchronized void newCharacter(CharacterEvent ce) {
curX = 0;
tmpChar[0] = (char) ce.character;
repaint( );
}
protected synchronized void paintComponent(Graphics gc) {
Dimension d = getSize( );
gc.clearRect(0, 0, d.width, d.height);
if (tmpChar[0] == 0)
return;
int charWidth = fm.charWidth(tmpChar[0]);
gc.drawChars(tmpChar, 0, 1,
curX++, fontHeight);
}
public void run( ) {
while (!done) {
repaint( );
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
return;
}
}
}
public void setDone(boolean b) {
done = b;
}
}AnimatedDisplayCanvas class
from the previous chapter:package javathreads.examples.ch02.example7;
private volatile boolean done = false;
private int curX = 0;
public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas
implements CharacterListener, Runnable {
...
public synchronized void newCharacter(CharacterEvent ce) {
curX = 0;
tmpChar[0] = (char) ce.character;
repaint( );
}
protected synchronized void paintComponent(Graphics gc) {
Dimension d = getSize( );
gc.clearRect(0, 0, d.width, d.height);
if (tmpChar[0] == 0)
return;
int charWidth = fm.charWidth(tmpChar[0]);
gc.drawChars(tmpChar, 0, 1,
curX++, fontHeight);
}
public void run( ) {
while (!done) {
repaint( );
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
return;
}
}
}
public void setDone(boolean b) {
done = b;
}
}run() method.
That thread is specifically created to wake up every 0.1 seconds to
send a repaint request to the system. To fulfill the repaint request,
the system—at a later time and in a different thread (the
event-dispatching thread, to be precise)—calls the
paintComponent() method to adjust and redraw the
canvas. This constant adjustment and redrawing is what is seen as
animation by the user.newCharacter() method is called
from the random character-generating thread (a character source)
whenever the character to be typed changes.newCharacter() method is accessing the same
data as the thread that calls the setDone() method. This method is called from the event-dispatching
thread when the Stop button is pressed; it is called by an event
handler (an actionPerformed() method) that is
defined as an inner class to the SwingTypeTester
class. The issue here is that this method is executed by the
event-dispatching thread and changes data that is being used by
another thread: the done flag, which is accessed
by the thread of the AnimatedDisplayCanvas class.synchronized keyword allows this problem to be
fixed. But no, the techniques that we have learned so far will not
work. The reason has to do with the run() method.
If we synchronized both the run() and
setDone() methods, how would the
setDone() method ever execute? The run(
) method does not exit until the done
flag is set, but the done flag
can't be set because the setDone() method can't execute until the
run() method completes.run() method is too large. By
synchronizing the run() method, the lock is
grabbed and never released. There is a way to shrink the scope of a
lock by synchronizing only the portion of the run() method that protects the done flag
(which we examine later in this chapter). However, there is a more
elegant solution in this case.setDone() method performs only one operation
with the package javathreads.examples.ch03.example1;
import javax.swing.*;
import java.awt.event.*;
import javathreads.examples.ch03.*;
public class ScoreLabel extends JLabel implements CharacterListener {
private volatile int score = 0;
private int char2type = -1;
private CharacterSource generator = null, typist = null;
public ScoreLabel (CharacterSource generator, CharacterSource typist) {
this.generator = generator;
this.typist = typist;
if (generator != null)
generator.addCharacterListener(this);
if (typist != null)
typist.addCharacterListener(this);
}
public ScoreLabel ( ) {
this(null, null);
}
public synchronized void resetGenerator(CharacterSource newGenerator) {
if (generator != null)
generator.removeCharacterListener(this);
generator = newGenerator;
if (generator != null)
generator.addCharacterListener(this);
}
public synchronized void resetTypist(CharacterSource newTypist) {
if (typist != null)
typist.removeCharacterListener(this);
typist = newTypist;
if (typist != null)
typist.addCharacterListener(this);
}
public synchronized void resetScore( ) {
score = 0;
char2type = -1;
setScore( );
}
private synchronized void setScore( ) {
// This method will be explained later in chapter 7
SwingUtilities.invokeLater(new Runnable( ) {
public void run( ) {
setText(Integer.toString(score));
}
});
}
public synchronized void newCharacter(CharacterEvent ce) {
// Previous character not typed correctly: 1-point penalty
if (ce.source == generator) {
if (char2type != -1) {
score--;
setScore( );
}
char2type = ce.character;
}
// If character is extraneous: 1-point penalty
// If character does not match: 1-point penalty
else {
if (char2type != ce.character) {
score--;
} else {
score++;
char2type = -1;
}
setScore( );
}
}
}synchronized
keyword
is to provide the ability to allow serialized entrance to
synchronized methods in an object. Although almost all the needs of
data protection can be accomplished with this keyword, it is too
primitive when the need for complex synchronization arises. More
complex cases can be handled by using classes that achieve similar
functionality as the synchronized keyword. These
classes are available beginning in J2SE 5.0, but alternatives for use
with earlier versions of Java are shown in Appendix A.Lock interface. For now, the two methods of
this interface that are important to us are lock(
) and unlock(). Using the
Lock interface is similar to using the
synchronized keyword: we call the lock() method at the start of the method and call the
unlock() method at the end of the method, and
we've effectively synchronized the method.lock() method grabs the
lock. The difference is that the lock can now be more easily
envisioned: we now have an actual object that represents the lock.
This object can be stored, passed around, and even discarded. As
before, if another thread owns the lock, a thread that attempts to
acquire the lock waits until the other thread calls the
unlock() method of the lock. Once that happens,
the waiting thread grabs the lock and returns from the lock(
) method. If another thread then wants the lock, it has to
wait until the current thread calls the unlock()
method. Let's implement
our scoring example using this new tool:package javathreads.examples.ch03.example2;
...
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class ScoreLabel extends JLabel implements CharacterListener {
...
private Lock scoreLock = new ReentrantLock( );
...
public void resetGenerator(CharacterSource newGenerator) {
try {
scoreLock.lock( );
if (generator != null)
generator.removeCharacterListener(this);
generator = newGenerator;
if (generator != null)
generator.addCharacterListener(this);
} finally {
scoreLock.unlock( );
}
}
public void resetTypist(CharacterSource newTypist) {
try {
scoreLock.lock( );
if (typist != null)
typist.removeCharacterListener(this);
typist = newTypist;
if (typist != null)
typist.addCharacterListener(this);
} finally {
scoreLock.unlock( );
}
}
public void resetScore( ) {
try {
scoreLock.lock( );
score = 0;
char2type = -1;
setScore( );
} finally {
scoreLock.unlock( );
}
}
private void setScore( ) {
// This method will be explained later in chapter 7
SwingUtilities.invokeLater(new Runnable( ) {
public void run( ) {
setText(Integer.toString(score));
}
});
}
public void newCharacter(CharacterEvent ce) {
try {
scoreLock.lock( );
// Previous character not typed correctly: 1-point penalty
if (ce.source == generator) {
if (char2type != -1) {
score--;
setScore( );
}
char2type = ce.character;
}
// If character is extraneous: 1-point penalty
// If character does not match: 1-point penalty
else {
if (char2type != ce.character) {
score--;
} else {
score++;
char2type = -1;
}
setScore( );
}
} finally {
scoreLock.unlock( );
}
}
}ScoreLabel class:package javathreads.examples.ch03.example3;
...
public class ScoreLabel extends JLabel implements CharacterListener {
...
public void newCharacter(CharacterEvent ce) {
if (ce.source == generator) {
try {
scoreLock.lock( );
// Previous character not typed correctly: 1-point penalty
if (char2type != -1) {
score--;
setScore( );
}
char2type = ce.character;
} finally {
scoreLock.unlock( );
}
}
// If character is extraneous: 1-point penalty
// If character does not match: 1-point penalty
else {
try {
scoreLock.lock( );
if (char2type != ce.character) {
score--;
} else {
score++;
char2type = -1;
}
setScore( );
} finally {
scoreLock.unlock( );
}
}
}
}lock() and unlock()
method calls are explicit, we can move them anywhere, establishing
any lock scope, from a single line of code to a scope that spans
multiple methods and objects. By providing the means of specifying
the scope of the lock, we can now move time-consuming and threadsafe
code outside of the lock scope. And we can now lock at a scope that
is specific to the program design instead of the object layout. In
this example, we moved the source check outside of the lock, and we
also split the lock in two, one for each of the conditions.synchronized
keyword
to lock a block of code within a method. It is also possible for the
synchronized keyword to specify the object whose
lock is grabbed instead of using the lock of the object that contains
the method. Much of what we accomplish with the
ScoreLabel class (using
synchronized methods) to our second (using an explicit lock),
it's easy to conclude that using the explicit lock
is not as easy as using the synchronized keyword.
With the keyword, we didn't need to create the lock
object, we didn't need to call the lock object to
grab and release the lock, and we didn't need to
worry about exceptions (therefore, we didn't need
the try/finally clause). So, which technique
should you use? That is up to you as a developer. It is possible to
use explicit locking for everything. It is possible to code to just
use the synchronized keyword. And it is possible
to use a combination of both. For more complex thread programming,
however, relying solely on the synchronized
keyword becomes very difficult, as we will see.synchronized keyword. Lock objects are
independent of the objects (and consequently, methods) that use them.
As far as lock objects are concerned, it doesn't
matter if the method being executed is static or not. As long as the
method has a reference to the lock object, it can acquire the lock.
For complex synchronization that involves both static and nonstatic
methods, it may be easier to use a lock object instead of the
synchronized
keyword.newCharacter() method could be refactored into multiple methods. This
isolates the generator and typist logic into separate methods, making
the code easier to maintain.package javathreads.examples.ch03.example5;
...
private synchronized void newGeneratorCharacter(int c) {
if (char2type != -1) {
score--;
setScore( );
}
char2type = c;
}
private synchronized void newTypistCharacter(int c) {
if (char2type != c) {
score--;
} else {
score++;
char2type = -1;
}
setScore( );
}
public synchronized void newCharacter(CharacterEvent ce) {
// Previous character not typed correctly: 1-point penalty
if (ce.source == generator) {
newGeneratorCharacter(ce.character);
}
// If character is extraneous: 1-point penalty
// If character does not match: 1-point penalty
else {
newTypistCharacter(ce.character);
}
}
}newGeneratorCharacter() and
newTypistCharacter()) are synchronized because
they access the shared state of the object. However, in this case,
synchronizing the methods is not technically necessary. Unlike the
other methods that access the shared data, these methods are private;
they can be called only from other methods of the class. Within the
class, they are called only from synchronized methods. So, there is
no reason for these methods to acquire the lock because all calls to
the method already own the lock. Yet it's still a
good idea to synchronize methods like this. Developers who modify
this class may not realize that their new code needs to obtain the
object lock before calling one of these new methods.AnimatedCharacterDisplayCanvas class. This class
uses a done flag to determine whether the
animation should be stopped. The previous example of this class
declares the done flag as volatile. This step was
necessary to allow atomic access to the variable to function
correctly. In this example, we incorrectly synchronize the methods.package javathreads.examples.ch03.example6;
...
public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas
implements CharacterListener, Runnable {
private boolean done = false;
...
protected synchronized void paintComponent(Graphics gc) {
...
}
public synchronized void run( ) {
while (!done) {
repaint( );
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
return;
}
}
}
public synchronized void setDone(boolean b) {
done = b;
}
}setDone() method. Only one lock is shared between
these threads: the lock attached to the object (the instance of the
ReentrantLock class allows the developer to
request that locks be granted fairly. This just means that locks are
granted in as close to arrival order as possible. While this is fair
for the majority of programs, the definition of
"fair" can be much more complex.synchronized keyword or explicit locks) is closest
to the last view. Java synchronization primitives are not designed to
grant locks for a particular situation — they are part of a
general purpose threads library. So, there is no reason that the
locks should be granted based on arrival order. Locks are granted
based on implementation-specific behavior of the underlying threading
system, but it is possible to base the lock acquisitions of the
ReentrantLock class on arrival order.synchronized keyword of the Java language. This
keyword allows us to synchronize methods and blocks of code.
We've also examined the basic synchronization
classes provided by the Java class library — the
ReentrantLock class and the
Lock interface. These classes allow us to lock
objects across methods and to acquire and release the lock at will
based on external events. They also provide features such as testing
to see if the lock is available, placin