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 dont have to think about it, which makes programming with multiple threads a much easier task. Comment
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. Comment
There are many possible uses for multithreading, but in general, youll have some part of your program tied to a particular event or resource, and you dont 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 buttonyou dont 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. Comment
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. Comment
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 youre explicitly using threads, you can produce the current thread used by your program with Thread and the static sleep( ) method. Comment
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. Comment
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 its in an infinite loop, and this means that actionPerformed( ) never returns. Since youre stuck inside actionPerformed( ) for the first keypress, the program cant 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.) Comment
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. Comment
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 CPUs 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. Comment
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. Comment
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. Comment
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 Threads 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. // {NoAutomaticTesting} 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. Comment
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 dont call start( ) (which you can do in the constructor, if thats appropriate) the thread will never be started. Comment
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)
Youll notice that nowhere in this example is sleep( ) called, and yet the output indicates that each thread gets a portion of the CPUs 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. Its simply another method. Comment
You can also see that the threads are not run in the order that theyre 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 Threads setPriority( ) method. Comment
When main( ) creates the Thread objects it isnt 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 cant clean it up. Comment
Now its possible to solve the problem in Counter1.java with a thread. The trick is to place the subtaskthat is, the loop thats 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. Heres 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() { super.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. Comment
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. Comment
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 permissionand its always good to make fields as private as possible so they cannot be accidentally changed by forces outside your class. Comment
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 isnt really instant, not like that of a system thats driven by interrupts. The counter stops only when the thread has the CPU and notices that the flag has changed. Comment
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. Comment
In the example above you can see that the thread class is separate from the programs 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. Comment
The use of the combined program/thread is not quite so obvious. When you start the program, you create an object thats Runnable, but you dont 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 its still dormant after init( ) completes. When you press the start button, the thread is created (if it doesnt already exist) in the somewhat obscure expression: Comment
new Thread(Counter3.this);
When something has a Runnable interface, it simply means that it has a run( ) method, but theres nothing special about thatit doesnt 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: Comment
selfThread.start();
This performs the usual initialization and then calls run( ). Comment
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[81]. Comment
Consider the creation of many different threads. You cant 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 youll often see, I cant recommend it for most cases because its just a little bit more confusing and less flexible. Comment
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. Comment
In Counter4 theres 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. Heres 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 youll be referring to in your program, and value can be any string, not just something that resolves to a number. Comment
Youll 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 youll 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( ). Comment
In addition, this code is set up to be either an applet or an application. When its an application the size argument is extracted from the command line (or a default value is provided). Comment
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. Comment
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. Comment
The ToggleL listener simply inverts the flag in Ticker and when the associated thread next takes note it can react accordingly. Comment
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, youll 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. Comment
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. Comment
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 doesnt terminate. There is, for instance, a thread that runs main( ). Comment
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. Comment
The following example demonstrates daemon threads:
//: c14:Daemons.java // Daemonic behavior. // {RunByHand} 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. Comment
Theres 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.) Comment
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 theres 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. Comment
With multithreading, things arent 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 youll 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. Comment
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, theres another thread of class Watcher that is watching the counters to see if theyre 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 thats where the surprise comes in. Heres 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() { incrementAccess(); if(count1 != count2) l.setText("Unsynched"); } } class Watcher extends Thread { public Watcher() { super.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. Comment
Because a TwoCounter thread is started via a keypress by the user, its possible that start( ) could be called more than once. Its 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. Comment
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. Comment
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.) Comment
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 thats kept in the Sharing1 object. You can think of the Watcher as constantly peeking over the shoulders of the TwoCounter objects. Comment
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. Comment
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 youll 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). Comment
Heres 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 thats not important here). When you run the program, however, youll discover that count1 and count2 will be observed (by the Watchers) to be unequal at times! This is because of the nature of threadsthey 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. Comment
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). Thats the problem that youre dealing with. Comment
Sometimes you dont care if a resource is being accessed at the same time youre 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. Comment
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. Comment
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 objects 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 dont 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, theres a single lock thats 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). Comment
Theres 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. Comment
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. Comment
Armed with this new keyword it appears that the solution is at hand: well 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() { incrementAccess(); if(count1 != count2) l.setText("Unsynched"); } } class Watcher extends Thread { public Watcher() { super.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); } } ///:~
Youll 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 wont work right. Comment
Now a new issue arises. The Watcher can never get a peek at whats 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. Comment
What wed 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: Comment
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. Comment
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 youll 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( ). Comment
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. Comment
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 wouldnt work either.) But it turns out that acquiring a lock is not a cheap operationit 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 arent any collisions is an invitation to disaster. Comment
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:
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 hadnt been introduced yet) and making the event unicast. Heres 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. Comment
You can see that the method notifyListeners( ) is not synchronized. It can be called from more than one thread at a time. Its 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( ). Comment
The paintComponent( ) method is also not synchronized. Deciding whether to synchronize overridden methods is not as clear as when youre just adding your own methods. In this example it turns out that paint( ) seems to work OK whether its synchronized or not. But the issues you must consider are:
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. Comment
A thread can be in any one of four states:
The blocked state is the most interesting one, and is worth further examination. A thread can become blocked for five reasons: Comment
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, theres some reason that it cannot continue running. Comment
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. (Youll notice the Continued and Continuing tags that allow the code extraction tool to piece everything together.) Comment
Because this example demonstrates some deprecated methods, you will get deprecation messages when it is compiled. Comment
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. Comment
The indicator of change in Blockable is an int i, which will be incremented by the run( ) method of the derived class. Comment
Theres a thread of class Peeker that is started for each Blockable object, and the Peekers 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. Comment
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. Youll 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 its always inside run( ), the method never gives up the object lock and the Peeker is blocked. Comment
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 youll see that the Peeker continues running when you start the Sleeper2 thread. Comment
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 theres 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 youll 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. Comment
You should be aware that Java 2 deprecates the use of suspend( ) and resume( ), because suspend( ) holds the objects 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. Comment
In the first two examples, its 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, youll 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 its called within. Comment
Youll 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. Comment
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. Comment
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 firstto have something thats exclusively for threading as part of the universal base classits essential because they manipulate the lock thats also part of every object. As a result, you can put a wait( ) inside any synchronized method, regardless of whether theres 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 thats not synchronized, the program will compile, but when you run it youll 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 dont manipulate the lock. Comment
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 cant fool with someone elses 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 youll 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 its legal for it to call notify( ) for wn2 and you wont get the IllegalMonitorStateException. Comment
///: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 youve gotten to the point where youre waiting for some other condition, under the control of forces outside your thread, to change and you dont 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. Comment
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). Comment
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. Comment
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. Theres 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. Comment
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 thats 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). Comment
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. Comment
When the Blockable threads are initially created, each one automatically creates and starts its own Peeker. So youll 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 its essential to see this to understand that particular aspect of blocking. Comment
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, its 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 doesnt happen that often, but when it happens to you its frustrating to debug. Comment
There is no language support to help prevent deadlock; its up to you to avoid it by careful design. These are not comforting words to the person whos trying to debug a deadlocking program. Comment
One change that has been made in Java 2 to reduce the possibility of deadlock is the deprecation of Threads stop( ), suspend( ), resume( ), and destroy( ) methods. Comment
The reason that the stop( ) method is deprecated is because it doesnt 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. Comment
There are times when a thread blockssuch as when it is waiting for inputand it cannot poll a flag as it does in Blocking.java. In these cases, you still shouldnt 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 objects interrupt( ) method is called. The first time you press the button youll see that the thread quits, but after that theres no thread to kill so you just see that the button has been pressed. Comment
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, youll notice that the code organization is quite differentanonymous 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: Comment
//: 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() { super.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 calledsince 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( ). Comment
The destroy( ) method of Thread has never been implemented; its 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. Comment
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 Javas 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 showthey 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. Id rather deal with the discomfort than watch the language stagnate. Comment
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 doesnt mean that threads with lower priority dont get run (that is, you cant get deadlocked because of priorities). Lower priority threads just tend to run less often. Comment
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 arent interesting to you. Comment
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 youll 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 theres an extra JTextField for displaying the priority of the thread and two more buttons for incrementing and decrementing the priority. Comment
Also notice the use of yield( ), which voluntarily hands control back to the scheduler. Without this the multithreading mechanism still works, but youll 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. Comment
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 groups maximum priority. (The next section will describe thread groups.) Finally, the priorities of the parent thread groups are also displayed as labels. Comment
When you press an up or down button, that Ticker2s priority is fetched and incremented or decremented accordingly. Comment
When you run this program, youll notice several things. First of all, the thread groups 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. Comment
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 groups priority, but no higher. Now decrement the thread groups priority a couple of times. The thread priorities are unchanged, but if you try to modify them either up or down youll see that theyll automatically pop to the priority of the thread group. Also, new threads will still be given a default priority, even if thats higher than the group priority. (Thus the group priority is not a way to prevent new threads from having higher priorities than existing ones.) Comment
Finally, try to increment the group maximum priority. It cant be done. You can only reduce thread group maximum priorities, not increase them. Comment
The value of thread groups can be summed up by a quote from Joshua Bloch[82], the software architect at Sun who fixed the Java collections library in JDK 1.2:Comment
Thread groups are best viewed as an unsuccessful experiment, and you may simply ignore their existence.
If youve spent time and energy trying to figure out the value of thread groups (as I have), you may wonder why there was not some more official announcement from Sun on the topic, sooner than this (the same question could be asked about any number of other changes that have happened to Java over the years). The Nobel Laureate economist Joseph Stiglitz has a philosophy of life which would seem to apply here (and in a number of other places throughout the experience of Java. Well, why stop there? Ive consulted on more than a few projects where this has applied). Its called The Theory of Escalating Commitment:Comment
"The cost of continuing mistakes is borne by others, while the cost of admitting mistakes is borne by yourself."
There is one tiny remaining use for thread groups. If a thread in the group throws an uncaught exception, ThreadGroup.uncaughtException() is invoked, which prints a stack trace to the standard error stream. If you want to modify this behavior, you must override this method.Comment
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 youll 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. Comment
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 dont 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. Comment
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. Comment
paintComponent( ) is quite simpleit just sets the color to cColor and fills the entire JPanel with that color. Comment
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. Comment
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.) Comment
This program also makes an interesting benchmark, since it can show dramatic performance differences between one JVM threading implementation and another. Comment
At some point, youll find that ColorBoxes bogs down. On my machine, this occurred somewhere after a 10 x 10 grid. Why does this happen? Youre naturally suspicious that Swing might have something to do with it, so heres 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 theres a speedup well know it was because there were too many threads in the previous example:Comment
//: 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. Comment
CBox2 is similar to CBox: it paints itself with a randomly chosen color. But thats all a CBox2 does. All of the threading has been moved into CBoxList. Comment
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. Comment
As before, when you implement Runnable you dont 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. Comment
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 doesnt 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 dont have too many threads (whatever that turns out to mean for your particular program and platformhere, the slowdown in ColorBoxes appears to be caused by the fact that theres 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: Comment
Issues like this are one reason that multithreaded programming is often considered an art. Comment
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. Comment
The main drawbacks to multithreading are:
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 changethe heavy context switchmust exchange the full memory space. Comment
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, dont blame Java. Threads are tricky. Comment
One of the biggest difficulties with threads occurs because more than one thread might be sharing a resourcesuch as the memory in an objectand you must make sure that multiple threads dont 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. Comment
In addition, theres 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 problemat 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 youll 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. Comment
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. Comment
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, OReilly, 1997. Comment
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.
[81] Runnable was in Java 1.0, while inner classes were not introduced until Java 1.1, which may partially account for the existence of Runnable. Also, traditional multithreading architectures focused on a function to be run rather than an object. My preference is always to inherit from Thread if I can; it seems cleaner and more flexible to me.
[82] Effective Java, by Joshua Bloch, Addison-Wesley 2001, page 211.