isApplet = false;
Chapter 14: Multiple Threads
615
applet.numCounters =
(args.length == 0 ? 5 :
Integer.parseInt(args[0]));
applet.numObservers =
(args.length < 2 ? 5 :
Integer.parseInt(args[1]));
Frame aFrame = new Frame("Sharing1");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(350, applet.numCounters *100);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
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 Container in the TwoCounter constructor. Because this thread is started via a button press 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 that 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 future 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 “Observe” button, one or more observers are created and freed upon the unsuspecting TwoCounter threads.
Note that to run this as an applet in a browser, your Web page will need to contain the lines:
<applet code=Sharing1 width=650 height=500>
<param name=size value="20">
<param name=observers value="1">
</applet>
616
Thinking in Java
www.BruceEckel.com
You can change the width, height, and parameters to suit your experimental tastes. By changing the size and observers you’ll change the behavior of the program. You can also see that 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 Watcher) 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.
How Java shares resources