MindView Inc.
[ Viewing Hints ] [ Revision History ] [ Report an Error ]
[ 1st Edition ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Thinking in Java, 2nd edition, Revision 11

©2000 by Bruce Eckel

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]

14: Multiple Threads

Objects provide a way to divide a program into independent sections. Often, you also need to turn a program into separate, independently running subtasks.

Each of these independent subtasks is called a thread, and you program as if each thread runs by itself and has the CPU to itself. Some underlying mechanism is actually dividing up the CPU time for you, but in general, you don’t have to think about it, which makes programming with multiple threads a much easier task.

A process is a self-contained running program with its own address space. A multitasking operating system is capable of running more than one process (program) at a time, while making it look like each one is chugging along on its own, by periodically providing CPU cycles to each process. A thread is a single sequential flow of control within a process. A single process can thus have multiple concurrently executing threads.

There are many possible uses for multithreading, but in general, you’ll have some part of your program tied to a particular event or resource, and you don’t want to hang up the rest of your program because of that. So you create a thread associated with that event or resource and let it run independently of the main program. A good example is a “quit” button—you don’t want to be forced to poll the quit button in every piece of code you write in your program and yet you want the quit button to be responsive, as if you were checking it regularly. In fact, one of the most immediately compelling reasons for multithreading is to produce a responsive user interface.

Responsive user interfaces

As a starting point, consider a program that performs some CPU-intensive operation and thus ends up ignoring user input and being unresponsive. This one, a combined applet/application, will simply display the result of a running counter:

//: c14:Counter1.java
// A non-responsive user interface.
// <applet code=Counter1 width=300 height=100>
// </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Counter1 extends JApplet {
  private int count = 0;
  private JButton
    start = new JButton("Start"),
    onOff = new JButton("Toggle");
  private JTextField t = new JTextField(10);
  private boolean runFlag = true;
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
    start.addActionListener(new StartL());
    cp.add(start);
    onOff.addActionListener(new OnOffL());
    cp.add(onOff);
  }
  public void go() {
    while (true) {
      try {
        Thread.sleep(100);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
      if (runFlag)
        t.setText(Integer.toString(count++));
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      go();
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  public static void main(String[] args) {
    Console.run(new Counter1(), 300, 100);
  }
} ///:~

At this point, the Swing and applet code should be reasonably familiar from Chapter 13. The go( ) method is where the program stays busy: it puts the current value of count into the JTextField t, then increments count.

Part of the infinite loop inside go( ) is to call sleep( ). sleep( ) must be associated with a Thread object, and it turns out that every application has some thread associated with it. (Indeed, Java is based on threads and there are always some running along with your application.) So regardless of whether you’re explicitly using threads, you can produce the current thread used by your program with Thread and the static sleep( ) method.

Note that sleep( ) can throw an InterruptedException, although throwing such an exception is considered a hostile way to break from a thread and should be discouraged. (Once again, exceptions are for exceptional conditions, not normal flow of control.) Interrupting a sleeping thread is included to support a future language feature.

When the start button is pressed, go( ) is invoked. On examining go( ), you might naively think (as I did) that it should allow multithreading because it goes to sleep. That is, while the method is asleep, it seems like the CPU could be busy monitoring other button presses. But it turns out that the real problem is that go( ) never returns, since it’s in an infinite loop, and this means that actionPerformed( ) never returns. Since you’re stuck inside actionPerformed( ) for the first keypress, the program can’t handle any other events. (To get out, you must somehow kill the process; the easiest way to do this is to press Control-C in the console window, if you started it from the console. If you start it via the browser, you have to kill the browser window.)

The basic problem here is that go( ) needs to continue performing its operations, and at the same time it needs to return so that actionPerformed( ) can complete and the user interface can continue responding to the user. But in a conventional method like go( ) it cannot continue and at the same time return control to the rest of the program. This sounds like an impossible thing to accomplish, as if the CPU must be in two places at once, but this is precisely the illusion that threading provides.

The thread model (and its programming support in Java) is a programming convenience to simplify juggling several operations at the same time within a single program. With threads, the CPU will pop around and give each thread some of its time. Each thread has the consciousness of constantly having the CPU to itself, but the CPU’s time is actually sliced between all the threads. The exception to this is if your program is running on multiple CPUs. But one of the great things about threading is that you are abstracted away from this layer, so your code does not need to know whether it is actually running on a single CPU or many. Thus, threads are a way to create transparently scalable programs.

Threading reduces computing efficiency somewhat, but the net improvement in program design, resource balancing, and user convenience is often quite valuable. Of course, if you have more than one CPU, then the operating system can dedicate each CPU to a set of threads or even a single thread and the whole program can run much faster. Multitasking and multithreading tend to be the most reasonable ways to utilize multiprocessor systems.

Inheriting from Thread

The simplest way to create a thread is to inherit from class Thread, which has all the wiring necessary to create and run threads. The most important method for Thread is run( ), which you must override to make the thread do your bidding. Thus, run( ) is the code that will be executed “simultaneously” with the other threads in a program.

The following example creates any number of threads that it keeps track of by assigning each thread a unique number, generated with a static variable. The Thread’s run( ) method is overridden to count down each time it passes through its loop and to finish when the count is zero (at the point when run( ) returns, the thread is terminated).

//: c14:SimpleThread.java
// Very simple Threading example.

public class SimpleThread extends Thread {
  private int countDown = 5;
  private static int threadCount = 0;
  private int threadNumber = ++threadCount;
  public SimpleThread() {
    System.out.println("Making " + threadNumber);
  }
  public void run() {
    while(true) {
      System.out.println("Thread " + 
        threadNumber + "(" + countDown + ")");
      if(--countDown == 0) return;
    }
  }
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++)
      new SimpleThread().start();
    System.out.println("All Threads Started");
  }
} ///:~

A run( ) method virtually always has some kind of loop that continues until the thread is no longer necessary, so you must establish the condition on which to break out of this loop (or, in the case above, simply return from run( )). Often, run( ) is cast in the form of an infinite loop, which means that, barring some external factor that causes run( ) to terminate, it will continue forever.

In main( ) you can see a number of threads being created and run. The start( ) method in the Thread class performs special initialization for the thread and then calls run( ). So the steps are: the constructor is called to build the object, then start( ) configures the thread and calls run( ). If you don’t call start( ) (which you can do in the constructor, if that’s appropriate) the thread will never be started.

The output for one run of this program (it will be different from one run to another) is:

Making 1
Making 2
Making 3
Making 4
Making 5
Thread 1(5)
Thread 1(4)
Thread 1(3)
Thread 1(2)
Thread 2(5)
Thread 2(4)
Thread 2(3)
Thread 2(2)
Thread 2(1)
Thread 1(1)
All Threads Started
Thread 3(5)
Thread 4(5)
Thread 4(4)
Thread 4(3)
Thread 4(2)
Thread 4(1)
Thread 5(5)
Thread 5(4)
Thread 5(3)
Thread 5(2)
Thread 5(1)
Thread 3(4)
Thread 3(3)
Thread 3(2)
Thread 3(1)

You’ll notice that nowhere in this example is sleep( ) called, and yet the output indicates that each thread gets a portion of the CPU’s time in which to execute. This shows that sleep( ), while it relies on the existence of a thread in order to execute, is not involved with either enabling or disabling threading. It’s simply another method.

You can also see that the threads are not run in the order that they’re created. In fact, the order that the CPU attends to an existing set of threads is indeterminate, unless you go in and adjust the priorities using Thread’s setPriority( ) method.

When main( ) creates the Thread objects it isn’t capturing the references for any of them. An ordinary object would be fair game for garbage collection, but not a Thread. Each Thread “registers” itself so there is actually a reference to it someplace and the garbage collector can’t clean it up.

Threading for a responsive interface

Now it’s possible to solve the problem in Counter1.java with a thread. The trick is to place the subtask—that is, the loop that’s inside go( )—inside the run( ) method of a thread. When the user presses the start button, the thread is started, but then the creation of the thread completes, so even though the thread is running, the main job of the program (watching for and responding to user-interface events) can continue. Here’s the solution:

//: c14:Counter2.java
// A responsive user interface with threads.
// <applet code=Counter2 width=300 height=100>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Counter2 extends JApplet {
  private class SeparateSubTask extends Thread {
    private int count = 0;
    private boolean runFlag = true;
    SeparateSubTask() { start(); }
    void invertFlag() { runFlag = !runFlag; }
    public void run() {
      while (true) {
       try {
        sleep(100);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
       if(runFlag) 
         t.setText(Integer.toString(count++));
      }
    }
  } 
  private SeparateSubTask sp = null;
  private JTextField t = new JTextField(10);
  private JButton 
    start = new JButton("Start"),
    onOff = new JButton("Toggle");
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(sp == null)
        sp = new SeparateSubTask();
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(sp != null)
        sp.invertFlag();
    }
  }
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
    start.addActionListener(new StartL());
    cp.add(start);
    onOff.addActionListener(new OnOffL());
    cp.add(onOff);
  }
  public static void main(String[] args) {
    Console.run(new Counter2 (), 300, 100);
  }
} ///:~

Counter2 is a straightforward program, whose only job is to set up and maintain the user interface. But now, when the user presses the start button, the event-handling code does not call a method. Instead a thread of class SeparateSubTask is created, and then the Counter2 event loop continues.

The class SeparateSubTask is a simple extension of Thread with a constructor that runs the thread by calling start( ), and a run( ) that essentially contains the “go( )” code from Counter1.java.

Because SeparateSubTask is an inner class, it can directly access the JTextField t in Counter2; you can see this happening inside run( ). The t field in the outer class is private since SeparateSubTask can access it without getting any special permission—and it’s always good to make fields “as private as possible” so they cannot be accidentally changed by forces outside your class.

When you press the onOff button it toggles the runFlag inside the SeparateSubTask object. That thread (when it looks at the flag) can then start and stop itself. Pressing the onOff button produces an apparently instant response. Of course, the response isn’t really instant, not like that of a system that’s driven by interrupts. The counter stops only when the thread has the CPU and notices that the flag has changed.

You can see that the inner class SeparateSubTask is private, which means that its fields and methods can be given default access (except for run( ), which must be public since it is public in the base class). The private inner class is not accessible to anyone but Counter2, and the two classes are tightly coupled. Anytime you notice classes that appear to have high coupling with each other, consider the coding and maintenance improvements you might get by using inner classes.

Combining the thread
with the main class

In the example above you can see that the thread class is separate from the program’s main class. This makes a lot of sense and is relatively easy to understand. There is, however, an alternate form that you will often see used that is not so clear but is usually more concise (which probably accounts for its popularity). This form combines the main program class with the thread class by making the main program class a thread. Since for a GUI program the main program class must be inherited from either Frame or Applet, an interface must be used to paste on the additional functionality. This interface is called Runnable, and it contains the same basic method that Thread does. In fact, Thread also implements Runnable, which specifies only that there be a run( ) method.

The use of the combined program/thread is not quite so obvious. When you start the program, you create an object that’s Runnable, but you don’t start the thread. This must be done explicitly. You can see this in the following program, which reproduces the functionality of Counter2:

//: c14:Counter3.java
// Using the Runnable interface to turn the 
// main class into a thread.
// <applet code=Counter3 width=300 height=100>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Counter3 
    extends JApplet implements Runnable {
  private int count = 0;
  private boolean runFlag = true;
  private Thread selfThread = null;
  private JButton 
    start = new JButton("Start"),
    onOff = new JButton("Toggle");
  private JTextField t = new JTextField(10);
  public void run() {
    while (true) {
      try {
        selfThread.sleep(100);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
      if(runFlag) 
        t.setText(Integer.toString(count++));
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(selfThread == null) {
        selfThread = new Thread(Counter3.this);
        selfThread.start();
      }
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
    start.addActionListener(new StartL());
    cp.add(start);
    onOff.addActionListener(new OnOffL());
    cp.add(onOff);
  }
  public static void main(String[] args) {
    Console.run(new Counter3(), 300, 100);
  }
} ///:~

Now the run( ) is inside the class, but it’s still dormant after init( ) completes. When you press the start button, the thread is created (if it doesn’t already exist) in the somewhat obscure expression:

new Thread(Counter3.this);

When something has a Runnable interface, it simply means that it has a run( ) method, but there’s nothing special about that—it doesn’t produce any innate threading abilities, like those of a class inherited from Thread. So to produce a thread from a Runnable object, you must create a separate Thread object as shown above, handing the Runnable object to the special Thread constructor. You can then call start( ) for that thread:

selfThread.start();

This performs the usual initialization and then calls run( ).

The convenient aspect about the Runnable interface is that everything belongs to the same class. If you need to access something, you simply do it without going through a separate object. However, as you saw in the previous example, this access is just as easy using an inner class[70].

Making many threads

Consider the creation of many different threads. You can’t do this with the previous example, so you must go back to having separate classes inherited from Thread to encapsulate the run( ). But this is a more general solution and easier to understand, so while the previous example shows a coding style you’ll often see, I can’t recommend it for most cases because it’s just a little bit more confusing and less flexible.

The following example repeats the form of the examples above with counters and toggle buttons. But now all the information for a particular counter, including the button and text field, is inside its own object that is inherited from Thread. All the fields in Ticker are private, which means that the Ticker implementation can be changed at will, including the quantity and type of data components to acquire and display information. When a Ticker object is created, the constructor adds its visual components to the content pane of the outer object:

//: c14:Counter4.java
// By keeping your thread as a distinct class,
// you can have as many threads as you want. 
// <applet code=Counter4 width=200 height=600>
// <param name=size value="12"></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Counter4 extends JApplet {
  private JButton start = new JButton("Start");
  private boolean started = false;
  private Ticker[] s;
  private boolean isApplet = true;
  private int size = 12;
  class Ticker extends Thread {
    private JButton b = new JButton("Toggle");
    private JTextField t = new JTextField(10);
    private int count = 0;
    private boolean runFlag = true;
    public Ticker() {
      b.addActionListener(new ToggleL());
      JPanel p = new JPanel();
      p.add(t);
      p.add(b);
      // Calls JApplet.getContentPane().add():
      getContentPane().add(p); 
    }
    class ToggleL implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        runFlag = !runFlag;
      }
    }
    public void run() {
      while (true) {
        if (runFlag)
          t.setText(Integer.toString(count++));
        try {
          sleep(100);
        } catch(InterruptedException e) {
          System.err.println("Interrupted");
        }
      }
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(!started) {
        started = true;
        for (int i = 0; i < s.length; i++)
          s[i].start();
      }
    }
  }
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    // Get parameter "size" from Web page:
    if (isApplet) {
      String sz = getParameter("size");
      if(sz != null)
        size = Integer.parseInt(sz);
    }
    s = new Ticker[size];
    for (int i = 0; i < s.length; i++)
      s[i] = new Ticker();
    start.addActionListener(new StartL());
    cp.add(start);
  }
  public static void main(String[] args) {
    Counter4 applet = new Counter4();
    // This isn't an applet, so set the flag and
    // produce the parameter values from args:
    applet.isApplet = false;
    if(args.length != 0)
      applet.size = Integer.parseInt(args[0]);
    Console.run(applet, 200, applet.size * 50);
  }
} ///:~

Ticker contains not only its threading equipment but also the way to control and display the thread. You can create as many threads as you want without explicitly creating the windowing components.

In Counter4 there’s an array of Ticker objects called s. For maximum flexibility, the size of this array is initialized by reaching out into the Web page using applet parameters. Here’s what the size parameter looks like on the page, embedded inside the applet tag:

<param name=size value="20">

The param, name, and value are all HTML keywords. name is what you’ll be referring to in your program, and value can be any string, not just something that resolves to a number.

You’ll notice that the determination of the size of the array s is done inside init( ), and not as part of an inline definition of s. That is, you cannot say as part of the class definition (outside of any methods):

int size = Integer.parseInt(getParameter("size"));
Ticker[] s = new Ticker[size];

You can compile this, but you’ll get a strange “null-pointer exception” at run-time. It works fine if you move the getParameter( ) initialization inside of init( ). The applet framework performs the necessary startup to grab the parameters before entering init( ).

In addition, this code is set up to be either an applet or an application. When it’s an application the size argument is extracted from the command line (or a default value is provided).

Once the size of the array is established, new Ticker objects are created; as part of the Ticker constructor the button and text field for each Ticker is added to the applet.

Pressing the start button means looping through the entire array of Tickers and calling start( ) for each one. Remember, start( ) performs necessary thread initialization and then calls run( ) for that thread.

The ToggleL listener simply inverts the flag in Ticker and when the associated thread next takes note it can react accordingly.

One value of this example is that it allows you to easily create large sets of independent subtasks and to monitor their behavior. In this case, you’ll see that as the number of subtasks gets larger, your machine will probably show more divergence in the displayed numbers because of the way that the threads are served.

You can also experiment to discover how important the sleep(100) is inside Ticker.run( ). If you remove the sleep( ), things will work fine until you press a toggle button. Then that particular thread has a false runFlag and the run( ) is just tied up in a tight infinite loop, which appears difficult to break during multithreading, so the responsiveness and speed of the program really bogs down.

Daemon threads

A “daemon” thread is one that is supposed to provide a general service in the background as long as the program is running, but is not part of the essence of the program. Thus, when all of the non-daemon threads complete, the program is terminated. Conversely, if there are any non-daemon threads still running, the program doesn’t terminate. (There is, for instance, a thread that runs main( ).)

You can find out if a thread is a daemon by calling isDaemon( ), and you can turn the “daemonhood” of a thread on and off with setDaemon( ). If a thread is a daemon, then any threads it creates will automatically be daemons.

The following example demonstrates daemon threads:

//: c14:Daemons.java
// Daemonic behavior.
import java.io.*;

class Daemon extends Thread {
  private static final int SIZE = 10;
  private Thread[] t = new Thread[SIZE];
  public Daemon() { 
    setDaemon(true);
    start();
  }
  public void run() {
    for(int i = 0; i < SIZE; i++)
      t[i] = new DaemonSpawn(i);
    for(int i = 0; i < SIZE; i++)
      System.out.println(
        "t[" + i + "].isDaemon() = " 
        + t[i].isDaemon());
    while(true) 
      yield();
  }
}

class DaemonSpawn extends Thread {
  public DaemonSpawn(int i) {
    System.out.println(
      "DaemonSpawn " + i + " started");
    start();
  }
  public void run() {
    while(true) 
      yield();
  }
}

public class Daemons {
  public static void main(String[] args) 
  throws IOException {
    Thread d = new Daemon();
    System.out.println(
      "d.isDaemon() = " + d.isDaemon());
    // Allow the daemon threads to
    // finish their startup processes:
    System.out.println("Press any key");
    System.in.read();
  }
} ///:~

The Daemon thread sets its daemon flag to “true” and then spawns a bunch of other threads to show that they are also daemons. Then it goes into an infinite loop that calls yield( ) to give up control to the other processes. In an earlier version of this program, the infinite loops would increment int counters, but this seemed to bring the whole program to a stop. Using yield( ) makes the program quite peppy.

There’s nothing to keep the program from terminating once main( ) finishes its job, since there are nothing but daemon threads running. So that you can see the results of starting all the daemon threads, System.in is set up to read so the program waits for a keypress before terminating. Without this you see only some of the results from the creation of the daemon threads. (Try replacing the read( ) code with sleep( ) calls of various lengths to see this behavior.)

Sharing limited resources

You can think of a single-threaded program as one lonely entity moving around through your problem space and doing one thing at a time. Because there’s only one entity, you never have to think about the problem of two entities trying to use the same resource at the same time, like two people trying to park in the same space, walk through a door at the same time, or even talk at the same time.

With multithreading, things aren’t lonely anymore, but you now have the possibility of two or more threads trying to use the same limited resource at once. Colliding over a resource must be prevented or else you’ll have two threads trying to access the same bank account at the same time, print to the same printer, or adjust the same valve, etc.

Improperly accessing resources

Consider a variation on the counters that have been used so far in this chapter. In the following example, each thread contains two counters that are incremented and displayed inside run( ). In addition, there’s another thread of class Watcher that is watching the counters to see if they’re always equivalent. This seems like a needless activity, since looking at the code it appears obvious that the counters will always be the same. But that’s where the surprise comes in. Here’s the first version of the program:

//: c14:Sharing1.java
// Problems with resource sharing while threading.
// <applet code=Sharing1 width=350 height=500>
// <param name=size value="12">
// <param name=watchers value="15">
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Sharing1 extends JApplet {
  private static int accessCount = 0;
  private static JTextField aCount = 
    new JTextField("0", 7);
  public static void incrementAccess() {
    accessCount++;
    aCount.setText(Integer.toString(accessCount));
  }
  private JButton 
    start = new JButton("Start"),
    watcher = new JButton("Watch");
  private boolean isApplet = true;
  private int numCounters = 12;
  private int numWatchers = 15;
  private TwoCounter[] s;
  class TwoCounter extends Thread {
    private boolean started = false;
    private JTextField 
      t1 = new JTextField(5),
      t2 = new JTextField(5);
    private JLabel l = 
      new JLabel("count1 == count2");
    private int count1 = 0, count2 = 0;
    // Add the display components as a panel:
    public TwoCounter() {
      JPanel p = new JPanel();
      p.add(t1);
      p.add(t2);
      p.add(l);
      getContentPane().add(p);
    }
    public void start() {
      if(!started) {
        started = true;
        super.start();
      }
    }
    public void run() {
      while (true) {
        t1.setText(Integer.toString(count1++));
        t2.setText(Integer.toString(count2++));
        try {
          sleep(500);
        } catch(InterruptedException e) {
          System.err.println("Interrupted");
        }
      }
    }
    public void synchTest() {
      Sharing1.incrementAccess();
      if(count1 != count2)
        l.setText("Unsynched");
    }
  }
  class Watcher extends Thread {
    public Watcher() { start(); }
    public void run() {
      while(true) {
        for(int i = 0; i < s.length; i++)
          s[i].synchTest();
        try {
          sleep(500);
        } catch(InterruptedException e) {
          System.err.println("Interrupted");
        }
      }
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      for(int i = 0; i < s.length; i++)
        s[i].start();
    }
  }
  class WatcherL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      for(int i = 0; i < numWatchers; i++)
        new Watcher();
    }
  }
  public void init() {
    if(isApplet) {
      String counters = getParameter("size");
      if(counters != null)
        numCounters = Integer.parseInt(counters);
      String watchers = getParameter("watchers");
      if(watchers != null)
        numWatchers = Integer.parseInt(watchers);
    }
    s = new TwoCounter[numCounters];
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    for(int i = 0; i < s.length; i++)
      s[i] = new TwoCounter();
    JPanel p = new JPanel();
    start.addActionListener(new StartL());
    p.add(start);
    watcher.addActionListener(new WatcherL());
    p.add(watcher);
    p.add(new JLabel("Access Count"));
    p.add(aCount);
    cp.add(p);
  }
  public static void main(String[] args) {
    Sharing1 applet = new Sharing1();
    // This isn't an applet, so set the flag and
    // produce the parameter values from args:
    applet.isApplet = false;
    applet.numCounters = 
      (args.length == 0 ? 12 :
        Integer.parseInt(args[0]));
    applet.numWatchers =
      (args.length < 2 ? 15 :
        Integer.parseInt(args[1]));
    Console.run(applet, 350, 
      applet.numCounters * 50);
  }
} ///:~

As before, each counter contains its own display components: two text fields and a label that initially indicates that the counts are equivalent. These components are added to the content pane of the outer class object in the TwoCounter constructor.

Because a TwoCounter thread is started via a keypress by the user, it’s possible that start( ) could be called more than once. It’s illegal for Thread.start( ) to be called more than once for a thread (an exception is thrown). You can see the machinery to prevent this in the started flag and the overridden start( ) method.

In run( ), count1 and count2 are incremented and displayed in a manner that would seem to keep them identical. Then sleep( ) is called; without this call the program balks because it becomes hard for the CPU to swap tasks.

The synchTest( ) method performs the apparently useless activity of checking to see if count1 is equivalent to count2; if they are not equivalent it sets the label to “Unsynched” to indicate this. But first, it calls a static member of the class Sharing1 that increments and displays an access counter to show how many times this check has occurred successfully. (The reason for this will become apparent in later variations of this example.)

The Watcher class is a thread whose job is to call synchTest( ) for all of the TwoCounter objects that are active. It does this by stepping through the array that’s kept in the Sharing1 object. You can think of the Watcher as constantly peeking over the shoulders of the TwoCounter objects.

Sharing1 contains an array of TwoCounter objects that it initializes in init( ) and starts as threads when you press the “start” button. Later, when you press the “Watch” button, one or more watchers are created and freed upon the unsuspecting TwoCounter threads.

Note that to run this as an applet in a browser, your applet tag will need to contain the lines:

<param name=size value="20">
<param name=watchers value="1">

You can experiment by changing the width, height, and parameters to suit your tastes. By changing the size and watchers you’ll change the behavior of the program. This program is set up to run as a stand-alone application by pulling the arguments from the command line (or providing defaults).

Here’s the surprising part. In TwoCounter.run( ), the infinite loop is just repeatedly passing over the adjacent lines:

t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));

(as well as sleeping, but that’s not important here). When you run the program, however, you’ll discover that count1 and count2 will be observed (by the Watchers) to be unequal at times! This is because of the nature of threads—they can be suspended at any time. So at times, the suspension occurs between the execution of the above two lines, and the Watcher thread happens to come along and perform the comparison at just this moment, thus finding the two counters to be different.

This example shows a fundamental problem with using threads. You never know when a thread might be run. Imagine sitting at a table with a fork, about to spear the last piece of food on your plate and as your fork reaches for it, the food suddenly vanishes (because your thread was suspended and another thread came in and stole the food). That’s the problem that you’re dealing with.

Sometimes you don’t care if a resource is being accessed at the same time you’re trying to use it (the food is on some other plate). But for multithreading to work, you need some way to prevent two threads from accessing the same resource, at least during critical periods.

Preventing this kind of collision is simply a matter of putting a lock on a resource when one thread is using it. The first thread that accesses a resource locks it, and then the other threads cannot access that resource until it is unlocked, at which time another thread locks and uses it, etc. If the front seat of the car is the limited resource, the child who shouts “Dibs!” asserts the lock.

How Java shares resources

Java has built-in support to prevent collisions over one kind of resource: the memory in an object. Since you typically make the data elements of a class private and access that memory only through methods, you can prevent collisions by making a particular method synchronized. Only one thread at a time can call a synchronized method for a particular object (although that thread can call more than one of the object’s synchronized methods). Here are simple synchronized methods:

synchronized void f() { /* ... */ }
synchronized void g(){ /* ... */ }

Each object contains a single lock (also called a monitor) that is automatically part of the object (you don’t have to write any special code). When you call any synchronized method, that object is locked and no other synchronized method of that object can be called until the first one finishes and releases the lock. In the example above, if f( ) is called for an object, g( ) cannot be called for the same object until f( ) is completed and releases the lock. Thus, there’s a single lock that’s shared by all the synchronized methods of a particular object, and this lock prevents common memory from being written by more than one method at a time (i.e., more than one thread at a time).

There’s also a single lock per class (as part of the Class object for the class), so that synchronized static methods can lock each other out from simultaneous access of static data on a class-wide basis.

Note that if you want to guard some other resource from simultaneous access by multiple threads, you can do so by forcing access to that resource through synchronized methods.

Synchronizing the counters

Armed with this new keyword it appears that the solution is at hand: we’ll simply use the synchronized keyword for the methods in TwoCounter. The following example is the same as the previous one, with the addition of the new keyword:

//: c14:Sharing2.java
// Using the synchronized keyword to prevent
// multiple access to a particular resource.
// <applet code=Sharing2 width=350 height=500>
// <param name=size value="12">
// <param name=watchers value="15">
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Sharing2 extends JApplet {
  TwoCounter[] s;
  private static int accessCount = 0;
  private static JTextField aCount = 
    new JTextField("0", 7);
  public static void incrementAccess() {
    accessCount++;
    aCount.setText(Integer.toString(accessCount));
  }
  private JButton 
    start = new JButton("Start"),
    watcher = new JButton("Watch");
  private boolean isApplet = true;
  private int numCounters = 12;
  private int numWatchers = 15;

  class TwoCounter extends Thread {
    private boolean started = false;
    private JTextField 
      t1 = new JTextField(5),
      t2 = new JTextField(5);
    private JLabel l = 
      new JLabel("count1 == count2");
    private int count1 = 0, count2 = 0;
    public TwoCounter() {
      JPanel p = new JPanel();
      p.add(t1);
      p.add(t2);
      p.add(l);
      getContentPane().add(p);
    }    
    public void start() {
      if(!started) {
        started = true;
        super.start();
      }
    }
    public synchronized void run() {
      while (true) {
        t1.setText(Integer.toString(count1++));
        t2.setText(Integer.toString(count2++));
        try {
          sleep(500);
        } catch(InterruptedException e) {
          System.err.println("Interrupted");
        }
      }
    }
    public synchronized void synchTest() {
      Sharing2.incrementAccess();
      if(count1 != count2)
        l.setText("Unsynched");
    }
  }
  
  class Watcher extends Thread {
    public Watcher() { start(); }
    public void run() {
      while(true) {
        for(int i = 0; i < s.length; i++)
          s[i].synchTest();
        try {
          sleep(500);
        } catch(InterruptedException e) {
          System.err.println("Interrupted");
        }
      }
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      for(int i = 0; i < s.length; i++)
        s[i].start();
    }
  }
  class WatcherL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      for(int i = 0; i < numWatchers; i++)
        new Watcher();
    }
  }
  public void init() {
    if(isApplet) {
      String counters = getParameter("size");
      if(counters != null)
        numCounters = Integer.parseInt(counters);
      String watchers = getParameter("watchers");
      if(watchers != null)
        numWatchers = Integer.parseInt(watchers);
    }
    s = new TwoCounter[numCounters];
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    for(int i = 0; i < s.length; i++)
      s[i] = new TwoCounter();
    JPanel p = new JPanel();
    start.addActionListener(new StartL());
    p.add(start);
    watcher.addActionListener(new WatcherL());
    p.add(watcher);
    p.add(new Label("Access Count"));
    p.add(aCount);
    cp.add(p);
  }
  public static void main(String[] args) {
    Sharing2 applet = new Sharing2();
    // This isn't an applet, so set the flag and
    // produce the parameter values from args:
    applet.isApplet = false;
    applet.numCounters = 
      (args.length == 0 ? 12 :
        Integer.parseInt(args[0]));
    applet.numWatchers =
      (args.length < 2 ? 15 :
        Integer.parseInt(args[1]));
    Console.run(applet, 350, 
      applet.numCounters * 50);
  }
} ///:~

You’ll notice that both run( ) and synchTest( ) are synchronized. If you synchronize only one of the methods, then the other is free to ignore the object lock and can be called with impunity. This is an important point: Every method that accesses a critical shared resource must be synchronized or it won’t work right.

Now a new issue arises. The Watcher can never get a peek at what’s going on because the entire run( ) method has been synchronized, and since run( ) is always running for each object the lock is always tied up and synchTest( ) can never be called. You can see this because the accessCount never changes.

What we’d like for this example is a way to isolate only part of the code inside run( ). The section of code you want to isolate this way is called a critical section and you use the synchronized keyword in a different way to set up a critical section. Java supports critical sections with the synchronized block; this time synchronized is used to specify the object whose lock is being used to synchronize the enclosed code:

synchronized(syncObject) {
  // This code can be accessed 
  // by only one thread at a time
}

Before the synchronized block can be entered, the lock must be acquired on syncObject. If some other thread already has this lock, then the block cannot be entered until the lock is given up.

The Sharing2 example can be modified by removing the synchronized keyword from the entire run( ) method and instead putting a synchronized block around the two critical lines. But what object should be used as the lock? The one that is already respected by synchTest( ), which is the current object (this)! So the modified run( ) looks like this:

  public void run() {
    while (true) {
      synchronized(this) {
        t1.setText(Integer.toString(count1++));
        t2.setText(Integer.toString(count2++));
      }
      try {
        sleep(500);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
    }
  }

This is the only change that must be made to Sharing2.java, and you’ll see that while the two counters are never out of synch (according to when the Watcher is allowed to look at them), there is still adequate access provided to the Watcher during the execution of run( ).

Of course, all synchronization depends on programmer diligence: every piece of code that can access a shared resource must be wrapped in an appropriate synchronized block.

Synchronized efficiency

Since having two methods write to the same piece of data never sounds like a particularly good idea, it might seem to make sense for all methods to be automatically synchronized and eliminate the synchronized keyword altogether. (Of course, the example with a synchronized run( ) shows that this wouldn’t work either.) But it turns out that acquiring a lock is not a cheap operation—it multiplies the cost of a method call (that is, entering and exiting from the method, not executing the body of the method) by a minimum of four times, and could be much more depending on your implementation. So if you know that a particular method will not cause contention problems it is expedient to leave off the synchronized keyword. On the other hand, leaving off the synchronized keyword because you think it is a performance bottleneck, and hoping that there aren’t any collisions is an invitation to disaster.

JavaBeans revisited

Now that you understand synchronization, you can take another look at JavaBeans. Whenever you create a Bean, you must assume that it will run in a multithreaded environment. This means that:

  1. Whenever possible, all the public methods of a Bean should be synchronized. Of course, this incurs the synchronized run-time overhead. If that’s a problem, methods that will not cause problems in critical sections can be left un-synchronized, but keep in mind that this is not always obvious. Methods that qualify tend to be small (such as getCircleSize( ) in the following example) and/or “atomic,” that is, the method call executes in such a short amount of code that the object cannot be changed during execution. Making such methods un-synchronized might not have a significant effect on the execution speed of your program. You might as well make all public methods of a Bean synchronized and remove the synchronized keyword only when you know for sure that it’s necessary and that it makes a difference.
  2. When firing a multicast event to a bunch of listeners interested in that event, you must assume that listeners might be added or removed while moving through the list.

The first point is fairly easy to deal with, but the second point requires a little more thought. Consider the BangBean.java example presented in the last chapter. That ducked out of the multithreading question by ignoring the synchronized keyword (which hadn’t been introduced yet) and making the event unicast. Here’s that example modified to work in a multithreaded environment and to use multicasting for events:

//: c14:BangBean2.java
// You should write your Beans this way so they 
// can run in a multithreaded environment.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import com.bruceeckel.swing.*;

public class BangBean2 extends JPanel 
    implements Serializable {
  private int xm, ym;
  private int cSize = 20; // Circle size
  private String text = "Bang!";
  private int fontSize = 48;
  private Color tColor = Color.red;
  private ArrayList actionListeners = 
    new ArrayList();
  public BangBean2() {
    addMouseListener(new ML());
    addMouseMotionListener(new MM());
  }
  public synchronized int getCircleSize() { 
    return cSize; 
  }
  public synchronized void 
  setCircleSize(int newSize) {
    cSize = newSize;
  }
  public synchronized String getBangText() { 
    return text; 
  }
  public synchronized void 
  setBangText(String newText) {
    text = newText;
  }
  public synchronized int getFontSize() { 
    return fontSize; 
  }
  public synchronized void 
  setFontSize(int newSize) {
    fontSize = newSize;
  }
  public synchronized Color getTextColor() {
    return tColor; 
  }
  public synchronized void 
  setTextColor(Color newColor) {
    tColor = newColor;
  }
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(Color.black);
    g.drawOval(xm - cSize/2, ym - cSize/2, 
      cSize, cSize);
  }
  // This is a multicast listener, which is
  // more typically used than the unicast
  // approach taken in BangBean.java:
  public synchronized void 
    addActionListener(ActionListener l) {
    actionListeners.add(l);
  }
  public synchronized void 
    removeActionListener(ActionListener l) {
    actionListeners.remove(l);
  }
  // Notice this isn't synchronized:
  public void notifyListeners() {
    ActionEvent a =
      new ActionEvent(BangBean2.this,
        ActionEvent.ACTION_PERFORMED, null);
    ArrayList lv = null;
    // Make a shallow copy of the List in case 
    // someone adds a listener while we're 
    // calling listeners:
    synchronized(this) {
      lv = (ArrayList)actionListeners.clone();
    }
    // Call all the listener methods:
    for(int i = 0; i < lv.size(); i++)
      ((ActionListener)lv.get(i))
        .actionPerformed(a);
  }
  class ML extends MouseAdapter {
    public void mousePressed(MouseEvent e) {
      Graphics g = getGraphics();
      g.setColor(tColor);
      g.setFont(
        new Font(
          "TimesRoman", Font.BOLD, fontSize));
      int width = 
        g.getFontMetrics().stringWidth(text);
      g.drawString(text, 
        (getSize().width - width) /2,
        getSize().height/2);
      g.dispose();
      notifyListeners();
    }
  }
  class MM extends MouseMotionAdapter {
    public void mouseMoved(MouseEvent e) {
      xm = e.getX();
      ym = e.getY();
      repaint();
    }
  }
  public static void main(String[] args) {
    BangBean2 bb = new BangBean2();
    bb.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        System.out.println("ActionEvent" + e);
      }
    });
    bb.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        System.out.println("BangBean2 action");
      }
    });
    bb.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        System.out.println("More action");
      }
    });
    Console.run(bb, 300, 300);
  }
} ///:~

Adding synchronized to the methods is an easy change. However, notice in addActionListener( ) and removeActionListener( ) that the ActionListeners are now added to and removed from an ArrayList, so you can have as many as you want.

You can see that the method notifyListeners( ) is not synchronized. It can be called from more than one thread at a time. It’s also possible for addActionListener( ) or removeActionListener( ) to be called in the middle of a call to notifyListeners( ), which is a problem since it traverses the ArrayList actionListeners. To alleviate the problem, the ArrayList is cloned inside a synchronized clause and the clone is traversed (see Appendix A for details of cloning). This way the original ArrayList can be manipulated without impact on notifyListeners( ).

The paintComponent( ) method is also not synchronized. Deciding whether to synchronize overridden methods is not as clear as when you’re just adding your own methods. In this example it turns out that paint( ) seems to work OK whether it’s synchronized or not. But the issues you must consider are:

  1. Does the method modify the state of “critical” variables within the object? To discover whether the variables are “critical” you must determine whether they will be read or set by other threads in the program. (In this case, the reading or setting is virtually always accomplished via synchronized methods, so you can just examine those.) In the case of paint( ), no modification takes place.
  2. Does the method depend on the state of these “critical” variables? If a synchronized method modifies a variable that your method uses, then you might very well want to make your method synchronized as well. Based on this, you might observe that cSize is changed by synchronized methods and therefore paint( ) should be synchronized. Here, however, you can ask “What’s the worst thing that will happen if cSize is changed during a paint( )?” When you see that it’s nothing too bad, and a transient effect at that, you can decide to leave paint( ) un-synchronized to prevent the extra overhead from the synchronized method call.
  3. A third clue is to notice whether the base-class version of paint( ) is synchronized, which it isn’t. This isn’t an airtight argument, just a clue. In this case, for example, a field that is changed via synchronized methods (that is cSize) has been mixed into the paint( ) formula and might have changed the situation. Notice, however, that synchronized doesn’t inherit—that is, if a method is synchronized in the base class then it is not automatically synchronized in the derived class overridden version.

The test code in TestBangBean2 has been modified from that in the previous chapter to demonstrate the multicast ability of BangBean2 by adding extra listeners.

Blocking

A thread can be in any one of four states:

  1. New: The thread object has been created but it hasn’t been started yet so it cannot run.
  2. Runnable: This means that a thread can be run when the time-slicing mechanism has CPU cycles available for the thread. Thus, the thread might or might not be running, but there’s nothing to prevent it from being run if the scheduler can arrange it; it’s not dead or blocked.
  3. Dead: The normal way for a thread to die is by returning from its run( ) method. You can also call stop( ), but this throws an exception that’s a subclass of Error (which means you aren’t forced to put the call in a try block). Remember that throwing an exception should be a special event and not part of normal program execution; thus the use of stop( ) is deprecated in Java 2. There’s also a destroy( ) method (which has never been implemented) that you should never call if you can avoid it since it’s drastic and doesn’t release object locks.
  4. Blocked: The thread could be run but there’s something that prevents it. While a thread is in the blocked state the scheduler will simply skip over it and not give it any CPU time. Until a thread reenters the runnable state it won’t perform any operations.

Becoming blocked

The blocked state is the most interesting one, and is worth further examination. A thread can become blocked for five reasons:

  1. You’ve put the thread to sleep by calling sleep(milliseconds), in which case it will not be run for the specified time.
  2. You’ve suspended the execution of the thread with suspend( ). It will not become runnable again until the thread gets the resume( ) message (these are deprecated in Java 2, and will be examined further).
  3. You’ve suspended the execution of the thread with wait( ). It will not become runnable again until the thread gets the notify( ) or notifyAll( ) message. (Yes, this looks just like number 2, but there’s a distinct difference that will be revealed.)
  4. The thread is waiting for some I/O to complete.
  5. The thread is trying to call a synchronized method on another object, and that object’s lock is not available.

You can also call yield( ) (a method of the Thread class) to voluntarily give up the CPU so that other threads can run. However, the same thing happens if the scheduler decides that your thread has had enough time and jumps to another thread. That is, nothing prevents the scheduler from moving your thread and giving time to some other thread. When a thread is blocked, there’s some reason that it cannot continue running.

The following example shows all five ways of becoming blocked. It all exists in a single file called Blocking.java, but it will be examined here in discrete pieces. (You’ll notice the “Continued” and “Continuing” tags that allow the code extraction tool to piece everything together.)

Because this example demonstrates some deprecated methods, you will get deprecation messages when it is compiled.

First, the basic framework:

//: c14:Blocking.java
// Demonstrates the various ways a thread
// can be blocked.
// <applet code=Blocking width=350 height=550>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import com.bruceeckel.swing.*;

//////////// The basic framework ///////////
class Blockable extends Thread {
  private Peeker peeker;
  protected JTextField state = new JTextField(30);
  protected int i;
  public Blockable(Container c) {
    c.add(state);
    peeker = new Peeker(this, c);
  }
  public synchronized int read() { return i; }
  protected synchronized void update() {
    state.setText(getClass().getName()
      + " state: i = " + i);
  }
  public void stopPeeker() { 
    // peeker.stop(); Deprecated in Java 1.2
    peeker.terminate(); // The preferred approach
  }
}

class Peeker extends Thread {
  private Blockable b;
  private int session;
  private JTextField status = new JTextField(30);
  private boolean stop = false;
  public Peeker(Blockable b, Container c) {
    c.add(status);
    this.b = b;
    start();
  }
  public void terminate() { stop = true; }
  public void run() {
    while (!stop) {
      status.setText(b.getClass().getName()
        + " Peeker " + (++session)
        + "; value = " + b.read());
       try {
        sleep(100);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
    }
  }
} ///:Continued

The Blockable class is meant to be a base class for all the classes in this example that demonstrate blocking. A Blockable object contains a JTextField called state that is used to display information about the object. The method that displays this information is update( ). You can see it uses getClass( ).getName( ) to produce the name of the class instead of just printing it out; this is because update( ) cannot know the exact name of the class it is called for, since it will be a class derived from Blockable.

The indicator of change in Blockable is an int i, which will be incremented by the run( ) method of the derived class.

There’s a thread of class Peeker that is started for each Blockable object, and the Peeker’s job is to watch its associated Blockable object to see changes in i by calling read( ) and reporting them in its status JTextField. This is important: Note that read( ) and update( ) are both synchronized, which means they require that the object lock be free.

Sleeping

The first test in this program is with sleep( ):

///:Continuing
///////////// Blocking via sleep() ///////////
class Sleeper1 extends Blockable {
  public Sleeper1(Container c) { super(c); }
  public synchronized void run() {
    while(true) {
      i++;
      update();
       try {
        sleep(1000);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
    }
  }
}
  
class Sleeper2 extends Blockable {
  public Sleeper2(Container c) { super(c); }
  public void run() {
    while(true) {
      change();
       try {
        sleep(1000);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
    }
  }
  public synchronized void change() {
      i++;
      update();
  }
} ///:Continued

In Sleeper1 the entire run( ) method is synchronized. You’ll see that the Peeker associated with this object will run along merrily until you start the thread, and then the Peeker stops cold. This is one form of blocking: since Sleeper1.run( ) is synchronized, and once the thread starts it’s always inside run( ), the method never gives up the object lock and the Peeker is blocked.

Sleeper2 provides a solution by making run( ) un-synchronized. Only the change( ) method is synchronized, which means that while run( ) is in sleep( ), the Peeker can access the synchronized method it needs, namely read( ). Here you’ll see that the Peeker continues running when you start the Sleeper2 thread.

Suspending and resuming

The next part of the example introduces the concept of suspension. The Thread class has a method suspend( ) to temporarily stop the thread and resume( ) that restarts it at the point it was halted. resume( ) must be called by some thread outside the suspended one, and in this case there’s a separate class called Resumer that does just that. Each of the classes demonstrating suspend/resume has an associated resumer:

///:Continuing
/////////// Blocking via suspend() ///////////
class SuspendResume extends Blockable {
  public SuspendResume(Container c) {
    super(c);    
    new Resumer(this); 
  }
}

class SuspendResume1 extends SuspendResume {
  public SuspendResume1(Container c) { super(c);}
  public synchronized void run() {
    while(true) {
      i++;
      update();
      suspend(); // Deprecated in Java 1.2
    }
  }
}

class SuspendResume2 extends SuspendResume {
  public SuspendResume2(Container c) { super(c);}
  public void run() {
    while(true) {
      change();
      suspend(); // Deprecated in Java 1.2
    }
  }
  public synchronized void change() {
      i++;
      update();
  }
}

class Resumer extends Thread {
  private SuspendResume sr;
  public Resumer(SuspendResume sr) {
    this.sr = sr;
    start();
  }
  public void run() {
    while(true) {
       try {
        sleep(1000);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
      sr.resume(); // Deprecated in Java 1.2
    }
  }
} ///:Continued

SuspendResume1 also has a synchronized run( ) method. Again, when you start this thread you’ll see that its associated Peeker gets blocked waiting for the lock to become available, which never happens. This is fixed as before in SuspendResume2, which does not synchronize the entire run( ) method but instead uses a separate synchronized change( ) method.

You should be aware that Java 2 deprecates the use of suspend( ) and resume( ), because suspend( ) holds the object’s lock and is thus deadlock-prone. That is, you can easily get a number of locked objects waiting on each other, and this will cause your program to freeze. Although you might see them used in older programs you should not use suspend( ) and resume( ). The proper solution is described later in this chapter.

Wait and notify

In the first two examples, it’s important to understand that both sleep( ) and suspend( ) do not release the lock as they are called. You must be aware of this when working with locks. On the other hand, the method wait( ) does release the lock when it is called, which means that other synchronized methods in the thread object could be called during a wait( ). In the following two classes, you’ll see that the run( ) method is fully synchronized in both cases, however, the Peeker still has full access to the synchronized methods during a wait( ). This is because wait( ) releases the lock on the object as it suspends the method it’s called within.

You’ll also see that there are two forms of wait( ). The first takes an argument in milliseconds that has the same meaning as in sleep( ): pause for this period of time. The difference is that in wait( ), the object lock is released and you can come out of the wait( ) because of a notify( ) as well as having the clock run out.

The second form takes no arguments, and means that the wait( ) will continue until a notify( ) comes along and will not automatically terminate after a time.

One fairly unique aspect of wait( ) and notify( ) is that both methods are part of the base class Object and not part of Thread as are sleep( ), suspend( ), and resume( ). Although this seems a bit strange at first—to have something that’s exclusively for threading as part of the universal base class—it’s essential because they manipulate the lock that’s also part of every object. As a result, you can put a wait( ) inside any synchronized method, regardless of whether there’s any threading going on inside that particular class. In fact, the only place you can call wait( ) is within a synchronized method or block. If you call wait( ) or notify( ) within a method that’s not synchronized, the program will compile, but when you run it you’ll get an IllegalMonitorStateException with the somewhat nonintuitive message “current thread not owner.” Note that sleep( ), suspend( ), and resume( ) can all be called within non-synchronized methods since they don’t manipulate the lock.

You can call wait( ) or notify( ) only for your own lock. Again, you can compile code that tries to use the wrong lock, but it will produce the same IllegalMonitorStateException message as before. You can’t fool with someone else’s lock, but you can ask another object to perform an operation that manipulates its own lock. So one approach is to create a synchronized method that calls notify( ) for its own object. However, in Notifier you’ll see the notify( ) call inside a synchronized block:

synchronized(wn2) {
  wn2.notify();
}

where wn2 is the object of type WaitNotify2. This method, which is not part of WaitNotify2, acquires the lock on the wn2 object, at which point it’s legal for it to call notify( ) for wn2 and you won’t get the IllegalMonitorStateException.

///:Continuing
/////////// Blocking via wait() ///////////
class WaitNotify1 extends Blockable {
  public WaitNotify1(Container c) { super(c); }
  public synchronized void run() {
    while(true) {
      i++;
      update();
       try {
        wait(1000);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
    }
  }
}

class WaitNotify2 extends Blockable {
  public WaitNotify2(Container c) {
    super(c);
    new Notifier(this); 
  }
  public synchronized void run() {
    while(true) {
      i++;
      update();
       try {
        wait();
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
    }
  }
}

class Notifier extends Thread {
  private WaitNotify2 wn2;
  public Notifier(WaitNotify2 wn2) {
    this.wn2 = wn2;
    start();
  }
  public void run() {
    while(true) {
       try {
        sleep(2000);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
      synchronized(wn2) {
        wn2.notify();
      }
    }
  }
}  ///:Continued

wait( ) is typically used when you’ve gotten to the point where you’re waiting for some other condition, under the control of forces outside your thread, to change and you don’t want to idly wait by inside the thread. So wait( ) allows you to put the thread to sleep while waiting for the world to change, and only when a notify( ) or notifyAll( ) occurs does the thread wake up and check for changes. Thus, it provides a way to synchronize between threads.

Blocking on I/O

If a stream is waiting for some I/O activity, it will automatically block. In the following portion of the example, the two classes work with generic Reader and Writer objects, but in the test framework a piped stream will be set up to allow the two threads to safely pass data to each other (which is the purpose of piped streams).

The Sender puts data into the Writer and sleeps for a random amount of time. However, Receiver has no sleep( ), suspend( ), or wait( ). But when it does a read( ) it automatically blocks when there is no more data.

///:Continuing
class Sender extends Blockable { // send
  private Writer out;
  public Sender(Container c, Writer out) { 
    super(c);
    this.out = out; 
  }
  public void run() {
    while(true) {
      for(char c = 'A'; c <= 'z'; c++) {
        try {
          i++;
          out.write(c);
          state.setText("Sender sent: " 
            + (char)c);
          sleep((int)(3000 * Math.random()));
        } catch(InterruptedException e) {
          System.err.println("Interrupted");
        } catch(IOException e) {
          System.err.println("IO problem");
        }
      }
    }
  }
}

class Receiver extends Blockable {
  private Reader in;
  public Receiver(Container c, Reader in) { 
    super(c);
    this.in = in; 
  }
  public void run() {
    try {
      while(true) {
        i++; // Show peeker it's alive
        // Blocks until characters are there:
        state.setText("Receiver read: "
          + (char)in.read());
      }
    } catch(IOException e) {
      System.err.println("IO problem");
    }
  }
} ///:Continued

Both classes also put information into their state fields and change i so the Peeker can see that the thread is running.

Testing

The main applet class is surprisingly simple because most of the work has been put into the Blockable framework. Basically, an array of Blockable objects is created, and since each one is a thread, they perform their own activities when you press the “start” button. There’s also a button and actionPerformed( ) clause to stop all of the Peeker objects, which provides a demonstration of the alternative to the deprecated (in Java 2) stop( ) method of Thread.

To set up a connection between the Sender and Receiver objects, a PipedWriter and PipedReader are created. Note that the PipedReader in must be connected to the PipedWriter out via a constructor argument. After that, anything that’s placed in out can later be extracted from in, as if it passed through a pipe (hence the name). The in and out objects are then passed to the Receiver and Sender constructors, respectively, which treat them as Reader and Writer objects of any type (that is, they are upcast).

The array of Blockable references b is not initialized at its point of definition because the piped streams cannot be set up before that definition takes place (the need for the try block prevents this).

///:Continuing
/////////// Testing Everything ///////////
public class Blocking extends JApplet {
  private JButton 
    start = new JButton("Start"),
    stopPeekers = new JButton("Stop Peekers");
  private boolean started = false;
  private Blockable[] b;
  private PipedWriter out;
  private PipedReader in;
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(!started) {
        started = true;
        for(int i = 0; i < b.length; i++)
          b[i].start();
      }
    }
  }
  class StopPeekersL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      // Demonstration of the preferred 
      // alternative to Thread.stop():
      for(int i = 0; i < b.length; i++)
        b[i].stopPeeker();
    }
  }
  public void init() {
     Container cp = getContentPane();
     cp.setLayout(new FlowLayout());
     out = new PipedWriter();
    try {
      in = new PipedReader(out);
    } catch(IOException e) {
      System.err.println("PipedReader problem");
    }
    b = new Blockable[] {
      new Sleeper1(cp),
      new Sleeper2(cp),
      new SuspendResume1(cp),
      new SuspendResume2(cp),
      new WaitNotify1(cp),
      new WaitNotify2(cp),
      new Sender(cp, out),
      new Receiver(cp, in)
    };
    start.addActionListener(new StartL());
    cp.add(start);
    stopPeekers.addActionListener(
      new StopPeekersL());
    cp.add(stopPeekers);
  }
  public static void main(String[] args) {
    Console.run(new Blocking(), 350, 550);
  }
} ///:~

In init( ), notice the loop that moves through the entire array and adds the state and peeker.status text fields to the page.

When the Blockable threads are initially created, each one automatically creates and starts its own Peeker. So you’ll see the Peekers running before the Blockable threads are started. This is important, as some of the Peekers will get blocked and stop when the Blockable threads start, and it’s essential to see this to understand that particular aspect of blocking.

Deadlock

Because threads can become blocked and because objects can have synchronized methods that prevent threads from accessing that object until the synchronization lock is released, it’s possible for one thread to get stuck waiting for another thread, which in turn waits for another thread, etc., until the chain leads back to a thread waiting on the first one. You get a continuous loop of threads waiting on each other and no one can move. This is called deadlock. The claim is that it doesn’t happen that often, but when it happens to you it’s frustrating to debug.

There is no language support to help prevent deadlock; it’s up to you to avoid it by careful design. These are not comforting words to the person who’s trying to debug a deadlocking program.

The deprecation of stop( ), suspend( ),
resume( ), and destroy( ) in Java 2

One change that has been made in Java 2 to reduce the possibility of deadlock is the deprecation of Thread’s stop( ), suspend( ), resume( ), and destroy( ) methods.

The reason that the stop( ) method is deprecated is because it doesn’t release the locks that the thread has acquired, and if the objects are in an inconsistent state (“damaged”) other threads can view and modify them in that state. The resulting problems can be subtle and difficult to detect. Instead of using stop( ), you should follow the example in Blocking.java and use a flag to tell the thread when to terminate itself by exiting its run( ) method.

There are times when a thread blocks—such as when it is waiting for input—and it cannot poll a flag as it does in Blocking.java. In these cases, you still shouldn’t use stop( ), but instead you can use the interrupt( ) method in Thread to break out of the blocked code:

//: c14:Interrupt.java
// The alternative approach to using 
// stop() when a thread is blocked.
// <applet code=Interrupt width=200 height=100>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

class Blocked extends Thread {
  public synchronized void run() {
    try {
      wait(); // Blocks
    } catch(InterruptedException e) {
      System.err.println("Interrupted");
    }
    System.out.println("Exiting run()");
  }
}

public class Interrupt extends JApplet {
  private JButton 
    interrupt = new JButton("Interrupt");
  private Blocked blocked = new Blocked();
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(interrupt);
    interrupt.addActionListener(
      new ActionListener() {
        public 
        void actionPerformed(ActionEvent e) {
          System.out.println("Button pressed");
          if(blocked == null) return;
          Thread remove = blocked;
          blocked = null; // to release it
          remove.interrupt();
        }
      });
    blocked.start();
  }
  public static void main(String[] args) {
    Console.run(new Interrupt(), 200, 100);
  }
} ///:~

The wait( ) inside Blocked.run( ) produces the blocked thread. When you press the button, the blocked reference is set to null so the garbage collector will clean it up, and then the object’s interrupt( ) method is called. The first time you press the button you’ll see that the thread quits, but after that there’s no thread to kill so you just see that the button has been pressed.

The suspend( ) and resume( ) methods turn out to be inherently deadlock-prone. When you call suspend( ), the target thread stops but it still holds any locks that it has acquired up to that point. So no other thread can access the locked resources until the thread is resumed. Any thread that wants to resume the target thread and also tries to use any of the locked resources produces deadlock. You should not use suspend( ) and resume( ), but instead put a flag in your Thread class to indicate whether the thread should be active or suspended. If the flag indicates that the thread is suspended, the thread goes into a wait using wait( ). When the flag indicates that the thread should be resumed the thread is restarted with notify( ). An example can be produced by modifying Counter2.java. Although the effect is similar, you’ll notice that the code organization is quite different—anonymous inner classes are used for all of the listeners and the Thread is an inner class, which makes programming slightly more convenient since it eliminates some of the extra bookkeeping necessary in Counter2.java:

//: c14:Suspend.java
// The alternative approach to using suspend()
// and resume(), which are deprecated in Java 2.
// <applet code=Suspend width=300 height=100>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Suspend extends JApplet {
  private JTextField t = new JTextField(10);
  private JButton 
    suspend = new JButton("Suspend"),
    resume = new JButton("Resume");
  private Suspendable ss = new Suspendable();
  class Suspendable extends Thread {
    private int count = 0;
    private boolean suspended = false;
    public Suspendable() { start(); }
    public void fauxSuspend() { 
      suspended = true;
    }
    public synchronized void fauxResume() {
      suspended = false;
      notify();
    }
    public void run() {
      while (true) {
        try {
          sleep(100);
          synchronized(this) {
            while(suspended)
              wait();
          }
        } catch(InterruptedException e) {
          System.err.println("Interrupted");
        }
        t.setText(Integer.toString(count++));
      }
    }
  } 
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
    suspend.addActionListener(
      new ActionListener() {
        public 
        void actionPerformed(ActionEvent e) {
          ss.fauxSuspend();
        }
      });
    cp.add(suspend);
    resume.addActionListener(
      new ActionListener() {
        public 
        void actionPerformed(ActionEvent e) {
          ss.fauxResume();
        }
      });
    cp.add(resume);
  }
  public static void main(String[] args) {
    Console.run(new Suspend(), 300, 100);
  }
} ///:~

The flag suspended inside Suspendable is used to turn suspension on and off. To suspend, the flag is set to true by calling fauxSuspend( ) and this is detected inside run( ). The wait( ), as described earlier in this chapter, must be synchronized so that it has the object lock. In fauxResume( ), the suspended flag is set to false and notify( ) is called—since this wakes up wait( ) inside a synchronized clause the fauxResume( ) method must also be synchronized so that it acquires the lock before calling notify( ) (thus the lock is available for the wait( ) to wake up with). If you follow the style shown in this program you can avoid using suspend( ) and resume( ).

The destroy( ) method of Thread has never been implemented; it’s like a suspend( ) that cannot resume, so it has the same deadlock issues as suspend( ). However, this is not a deprecated method and it might be implemented in a future version of Java (after 2) for special situations in which the risk of a deadlock is acceptable.

You might wonder why these methods, now deprecated, were included in Java in the first place. It seems an admission of a rather significant mistake to simply remove them outright (and pokes yet another hole in the arguments for Java’s exceptional design and infallibility touted by Sun marketing people). The heartening part about the change is that it clearly indicates that the technical people and not the marketing people are running the show—they discovered a problem and they are fixing it. I find this much more promising and hopeful than leaving the problem in because “fixing it would admit an error.” It means that Java will continue to improve, even if it means a little discomfort on the part of Java programmers. I’d rather deal with the discomfort than watch the language stagnate.

Priorities

The priority of a thread tells the scheduler how important this thread is. If there are a number of threads blocked and waiting to be run, the scheduler will run the one with the highest priority first. However, this doesn’t mean that threads with lower priority don’t get run (that is, you can’t get deadlocked because of priorities). Lower priority threads just tend to run less often.

Although priorities are interesting to know about and to play with, in practice you almost never need to set priorities yourself. So feel free to skip the rest of this section if priorities aren’t interesting to you.

Reading and setting priorities

You can read the priority of a thread with getPriority( ) and change it with setPriority( ). The form of the prior “counter” examples can be used to show the effect of changing the priorities. In this applet you’ll see that the counters slow down as the associated threads have their priorities lowered:

//: c14:Counter5.java
// Adjusting the priorities of threads.
// <applet code=Counter5 width=450 height=600>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

class Ticker2 extends Thread {
  private JButton
    b = new JButton("Toggle"),
    incPriority = new JButton("up"),
    decPriority = new JButton("down");
  private JTextField
    t = new JTextField(10),
    pr = new JTextField(3); // Display priority
  private int count = 0;
  private boolean runFlag = true;
  public Ticker2(Container c) {
    b.addActionListener(new ToggleL());
    incPriority.addActionListener(new UpL());
    decPriority.addActionListener(new DownL());
    JPanel p = new JPanel();
    p.add(t);
    p.add(pr);
    p.add(b);
    p.add(incPriority);
    p.add(decPriority);
    c.add(p);
  }
  class ToggleL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  class UpL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      int newPriority = getPriority() + 1;
      if(newPriority > Thread.MAX_PRIORITY)
        newPriority = Thread.MAX_PRIORITY;
      setPriority(newPriority);
    }
  }
  class DownL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      int newPriority = getPriority() - 1;
      if(newPriority < Thread.MIN_PRIORITY)
        newPriority = Thread.MIN_PRIORITY;
      setPriority(newPriority);
    }
  }
  public void run() {
    while (true) {
      if(runFlag) {
        t.setText(Integer.toString(count++));
        pr.setText(
          Integer.toString(getPriority()));
      }
      yield();
    }
  }
}

public class Counter5 extends JApplet {
  private JButton
    start = new JButton("Start"),
    upMax = new JButton("Inc Max Priority"),
    downMax = new JButton("Dec Max Priority");
  private boolean started = false;
  private static final int SIZE = 10;
  private Ticker2[] s = new Ticker2[SIZE];
  private JTextField mp = new JTextField(3);
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    for(int i = 0; i < s.length; i++)
      s[i] = new Ticker2(cp);
    cp.add(new JLabel(
      "MAX_PRIORITY = " + Thread.MAX_PRIORITY));
    cp.add(new JLabel("MIN_PRIORITY = "
      + Thread.MIN_PRIORITY));
    cp.add(new JLabel("Group Max Priority = "));
    cp.add(mp);
    cp.add(start);
    cp.add(upMax);
    cp.add(downMax);
    start.addActionListener(new StartL());
    upMax.addActionListener(new UpMaxL());
    downMax.addActionListener(new DownMaxL());
    showMaxPriority();
    // Recursively display parent thread groups:
    ThreadGroup parent =
      s[0].getThreadGroup().getParent();
    while(parent != null) {
      cp.add(new Label(
        "Parent threadgroup max priority = "
        + parent.getMaxPriority()));
      parent = parent.getParent();
    }
  }
  public void showMaxPriority() {
    mp.setText(Integer.toString(
      s[0].getThreadGroup().getMaxPriority()));
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(!started) {
        started = true;
        for(int i = 0; i < s.length; i++)
          s[i].start();
      }
    }
  }
  class UpMaxL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      int maxp =
        s[0].getThreadGroup().getMaxPriority();
      if(++maxp > Thread.MAX_PRIORITY)
        maxp = Thread.MAX_PRIORITY;
      s[0].getThreadGroup().setMaxPriority(maxp);
      showMaxPriority();
    }
  }
  class DownMaxL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      int maxp =
        s[0].getThreadGroup().getMaxPriority();
      if(--maxp < Thread.MIN_PRIORITY)
        maxp = Thread.MIN_PRIORITY;
      s[0].getThreadGroup().setMaxPriority(maxp);
      showMaxPriority();
    }
  }
  public static void main(String[] args) {
    Console.run(new Counter5(), 450, 600);
  }
} ///:~

Ticker2 follows the form established earlier in this chapter, but there’s an extra JTextField for displaying the priority of the thread and two more buttons for incrementing and decrementing the priority.

Also notice the use of yield( ), which voluntarily hands control back to the scheduler. Without this the multithreading mechanism still works, but you’ll notice it runs slowly (try removing the call to yield( ) to see this). You could also call sleep( ), but then the rate of counting would be controlled by the sleep( ) duration instead of the priority.

The init( ) in Counter5 creates an array of ten Ticker2s; their buttons and fields are placed on the form by the Ticker2 constructor. Counter5 adds buttons to start everything up as well as increment and decrement the maximum priority of the thread group. In addition, there are labels that display the maximum and minimum priorities possible for a thread and a JTextField to show the thread group’s maximum priority. (The next section will describe thread groups.) Finally, the priorities of the parent thread groups are also displayed as labels.

When you press an “up” or “down” button, that Ticker2’s priority is fetched and incremented or decremented accordingly.

When you run this program, you’ll notice several things. First of all, the thread group’s default priority is five. Even if you decrement the maximum priority below five before starting the threads (or before creating the threads, which requires a code change), each thread will have a default priority of five.

The simple test is to take one counter and decrement its priority to one, and observe that it counts much slower. But now try to increment it again. You can get it back up to the thread group’s priority, but no higher. Now decrement the thread group’s priority a couple of times. The thread priorities are unchanged, but if you try to modify them either up or down you’ll see that they’ll automatically pop to the priority of the thread group. Also, new threads will still be given a default priority, even if that’s higher than the group priority. (Thus the group priority is not a way to prevent new threads from having higher priorities than existing ones.)

Finally, try to increment the group maximum priority. It can’t be done. You can only reduce thread group maximum priorities, not increase them.

Thread groups

All threads belong to a thread group. This can be either the default thread group or a group you explicitly specify when you create the thread. At creation, the thread is bound to a group and cannot change to a different group. Each application has at least one thread that belongs to the system thread group. If you create more threads without specifying a group, they will also belong to the system thread group.

Thread groups must also belong to other thread groups. The thread group that a new one belongs to must be specified in the constructor. If you create a thread group without specifying a thread group for it to belong to, it will be placed under the system thread group. Thus, all thread groups in your application will ultimately have the system thread group as the parent.

The reason for the existence of thread groups is hard to determine from the literature, which tends to be confusing on this subject. It’s often cited as “security reasons.” According to Arnold & Gosling,[71] “Threads within a thread group can modify the other threads in the group, including any farther down the hierarchy. A thread cannot modify threads outside of its own group or contained groups.” It’s hard to know what “modify” is supposed to mean here. The following example shows a thread in a “leaf” subgroup modifying the priorities of all the threads in its tree of thread groups as well as calling a method for all the threads in its tree.

//: c14:TestAccess.java
// How threads can access other threads
// in a parent thread group.

public class TestAccess {
  public static void main(String[] args) {
    ThreadGroup 
      x = new ThreadGroup("x"),
      y = new ThreadGroup(x, "y"),
      z = new ThreadGroup(y, "z");
    Thread
      one = new TestThread1(x, "one"),
      two = new TestThread2(z, "two");
  }
}

class TestThread1 extends Thread {
  private int i;
  TestThread1(ThreadGroup g, String name) {
    super(g, name);
  }
  void f() {
    i++; // modify this thread
    System.out.println(getName() + " f()");
  }
}

class TestThread2 extends TestThread1 {
  TestThread2(ThreadGroup g, String name) {
    super(g, name);
    start();
  }
  public void run() {
    ThreadGroup g =
      getThreadGroup().getParent().getParent();
    g.list();
    Thread[] gAll = new Thread[g.activeCount()];
    g.enumerate(gAll);
    for(int i = 0; i < gAll.length; i++) {
      gAll[i].setPriority(Thread.MIN_PRIORITY);
      ((TestThread1)gAll[i]).f();
    }
    g.list();
  }
} ///:~

In main( ), several ThreadGroups are created, leafing off from each other: x has no argument but its name (a String), so it is automatically placed in the “system” thread group, while y is under x and z is under y. Note that initialization happens in textual order so this code is legal.

Two threads are created and placed in different thread groups. TestThread1 doesn’t have a run( ) method but it does have an f( ) that modifies the thread and prints something so you can see it was called. TestThread2 is a subclass of TestThread1 and its run( ) is fairly elaborate. It first gets the thread group of the current thread, then moves up the heritage tree by two levels using getParent( ). (This is contrived since I purposely place the TestThread2 object two levels down in the hierarchy.) At this point, an array of references to Threads is created using the method activeCount( ) to ask how many threads are in this thread group and all the child thread groups. The enumerate( ) method places references to all of these threads in the array gAll, then I simply move through the entire array calling the f( ) method for each thread, as well as modifying the priority. Thus, a thread in a “leaf” thread group modifies threads in parent thread groups.

The debugging method list( ) prints all the information about a thread group to standard output and is helpful when investigating thread group behavior. Here’s the output of the program:

java.lang.ThreadGroup[name=x,maxpri=10]
    Thread[one,5,x]
    java.lang.ThreadGroup[name=y,maxpri=10]
        java.lang.ThreadGroup[name=z,maxpri=10]
            Thread[two,5,z]
one f()
two f()
java.lang.ThreadGroup[name=x,maxpri=10]
    Thread[one,1,x]
    java.lang.ThreadGroup[name=y,maxpri=10]
        java.lang.ThreadGroup[name=z,maxpri=10]
            Thread[two,1,z]

Not only does list( ) print the class name of ThreadGroup or Thread, but it also prints the thread group name and its maximum priority. For threads, the thread name is printed, followed by the thread priority and the group that it belongs to. Note that list( ) indents the threads and thread groups to indicate that they are children of the unindented thread group.

You can see that f( ) is called by the TestThread2 run( ) method, so it’s obvious that all threads in a group are vulnerable. However, you can access only the threads that branch off from your own system thread group tree, and perhaps this is what is meant by “safety.” You cannot access anyone else’s system thread group tree.

Controlling thread groups

Putting aside the safety issue, one thing thread groups seem to be useful for is control: you can perform certain operations on an entire thread group with a single command. The following example demonstrates this, and the restrictions on priorities within thread groups. The commented numbers in parentheses provide a reference to compare to the output.

//: c14:ThreadGroup1.java
// How thread groups control priorities
// of the threads inside them.

public class ThreadGroup1 {
  public static void main(String[] args) {
    // Get the system thread & print its Info:
    ThreadGroup sys = 
      Thread.currentThread().getThreadGroup();
    sys.list(); // (1)
    // Reduce the system thread group priority:
    sys.setMaxPriority(Thread.MAX_PRIORITY - 1);
    // Increase the main thread priority:
    Thread curr = Thread.currentThread();
    curr.setPriority(curr.getPriority() + 1);
    sys.list(); // (2)
    // Attempt to set a new group to the max:
    ThreadGroup g1 = new ThreadGroup("g1");
    g1.setMaxPriority(Thread.MAX_PRIORITY);
    // Attempt to set a new thread to the max:
    Thread t = new Thread(g1, "A");
    t.setPriority(Thread.MAX_PRIORITY);
    g1.list(); // (3)
    // Reduce g1's max priority, then attempt
    // to increase it:
    g1.setMaxPriority(Thread.MAX_PRIORITY - 2);
    g1.setMaxPriority(Thread.MAX_PRIORITY);
    g1.list(); // (4)
    // Attempt to set a new thread to the max:
    t = new Thread(g1, "B");
    t.setPriority(Thread.MAX_PRIORITY);
    g1.list(); // (5)
    // Lower the max priority below the default
    // thread priority:
    g1.setMaxPriority(Thread.MIN_PRIORITY + 2);
    // Look at a new thread's priority before
    // and after changing it:
    t = new Thread(g1, "C");
    g1.list(); // (6)
    t.setPriority(t.getPriority() -1);
    g1.list(); // (7)
    // Make g2 a child Threadgroup of g1 and
    // try to increase its priority:
    ThreadGroup g2 = new ThreadGroup(g1, "g2");
    g2.list(); // (8)
    g2.setMaxPriority(Thread.MAX_PRIORITY);
    g2.list(); // (9)
    // Add a bunch of new threads to g2:
    for (int i = 0; i < 5; i++)
      new Thread(g2, Integer.toString(i));
    // Show information about all threadgroups
    // and threads:
    sys.list(); // (10)
    System.out.println("Starting all threads:");
    Thread[] all = new Thread[sys.activeCount()];
    sys.enumerate(all);
    for(int i = 0; i < all.length; i++)
      if(!all[i].isAlive())
        all[i].start();
    // Suspends & Stops all threads in 
    // this group and its subgroups:
    System.out.println("All threads started");
    sys.suspend(); // Deprecated in Java 2
    // Never gets here...
    System.out.println("All threads suspended");
    sys.stop(); // Deprecated in Java 2
    System.out.println("All threads stopped");
  }
} ///:~

The output that follows has been edited to allow it to fit on the page (the java.lang. has been removed) and to add numbers to correspond to the commented numbers in the listing above.

(1) ThreadGroup[name=system,maxpri=10]
      Thread[main,5,system]
(2) ThreadGroup[name=system,maxpri=9]
      Thread[main,6,system]
(3) ThreadGroup[name=g1,maxpri=9]
      Thread[A,9,g1]
(4) ThreadGroup[name=g1,maxpri=8]
      Thread[A,9,g1]
(5) ThreadGroup[name=g1,maxpri=8]
      Thread[A,9,g1]
      Thread[B,8,g1]
(6) ThreadGroup[name=g1,maxpri=3]
      Thread[A,9,g1]
      Thread[B,8,g1]
      Thread[C,6,g1]
(7) ThreadGroup[name=g1,maxpri=3]
      Thread[A,9,g1]
      Thread[B,8,g1]
      Thread[C,3,g1]
(8) ThreadGroup[name=g2,maxpri=3]
(9) ThreadGroup[name=g2,maxpri=3]
(10)ThreadGroup[name=system,maxpri=9]
      Thread[main,6,system]
      ThreadGroup[name=g1,maxpri=3]
        Thread[A,9,g1]
        Thread[B,8,g1]
        Thread[C,3,g1]
        ThreadGroup[name=g2,maxpri=3]
          Thread[0,6,g2]
          Thread[1,6,g2]
          Thread[2,6,g2]
          Thread[3,6,g2]
          Thread[4,6,g2]
Starting all threads:
All threads started

All programs have at least one thread running, and the first action in main( ) is to call the static method of Thread called currentThread( ). From this thread, the thread group is produced and list( ) is called for the result. The output is:

(1) ThreadGroup[name=system,maxpri=10]
      Thread[main,5,system]

You can see that the name of the main thread group is system, and the name of the main thread is main, and it belongs to the system thread group.

The second exercise shows that the system group’s maximum priority can be reduced and the main thread can have its priority increased:

(2) ThreadGroup[name=system,maxpri=9]
      Thread[main,6,system]

The third exercise creates a new thread group, g1, which automatically belongs to the system thread group since it isn’t otherwise specified. A new thread A is placed in g1. After attempting to set this group’s maximum priority to the highest level and A’s priority to the highest level, the result is:

(3) ThreadGroup[name=g1,maxpri=9]
      Thread[A,9,g1]

Thus, it’s not possible to change the thread group’s maximum priority to be higher than its parent thread group.

The fourth exercise reduces g1’s maximum priority by two and then tries to increase it up to Thread.MAX_PRIORITY. The result is:

(4) ThreadGroup[name=g1,maxpri=8]
      Thread[A,9,g1]

You can see that the increase in maximum priority didn’t work. You can only decrease a thread group’s maximum priority, not increase it. Also, notice that thread A’s priority didn’t change, and now it is higher than the thread group’s maximum priority. Changing a thread group’s maximum priority doesn’t affect existing threads.

The fifth exercise attempts to set a new thread to maximum priority:

(5) ThreadGroup[name=g1,maxpri=8]
      Thread[A,9,g1]
      Thread[B,8,g1]

The new thread cannot be changed to anything higher than the maximum thread group priority.

The default thread priority for this program is six; that’s the priority a new thread will be created at and where it will stay if you don’t manipulate the priority. Exercise 6 lowers the maximum thread group priority below the default thread priority to see what happens when you create a new thread under this condition:

(6) ThreadGroup[name=g1,maxpri=3]
      Thread[A,9,g1]
      Thread[B,8,g1]
      Thread[C,6,g1]

Even though the maximum priority of the thread group is three, the new thread is still created using the default priority of six. Thus, maximum thread group priority does not affect default priority. (In fact, there appears to be no way to set the default priority for new threads.)

After changing the priority, attempting to decrement it by one, the result is:

(7) ThreadGroup[name=g1,maxpri=3]
      Thread[A,9,g1]
      Thread[B,8,g1]
      Thread[C,3,g1]

Only when you attempt to change the priority is the thread group’s maximum priority enforced.

A similar experiment is performed in (8) and (9), in which a new thread group g2 is created as a child of g1 and its maximum priority is changed. You can see that it’s impossible for g2’s maximum to go higher than g1’s:

(8) ThreadGroup[name=g2,maxpri=3]
(9) ThreadGroup[name=g2,maxpri=3]

Also notice that g2 is automatically set to the thread group maximum priority of g1 as g2 is created.

After all of these experiments, the entire system of thread groups and threads is printed:

(10)ThreadGroup[name=system,maxpri=9]
      Thread[main,6,system]
      ThreadGroup[name=g1,maxpri=3]
        Thread[A,9,g1]
        Thread[B,8,g1]
        Thread[C,3,g1]
        ThreadGroup[name=g2,maxpri=3]
          Thread[0,6,g2]
          Thread[1,6,g2]
          Thread[2,6,g2]
          Thread[3,6,g2]
          Thread[4,6,g2]

So because of the rules of thread groups, a child group must always have a maximum priority that’s less than or equal to its parent’s maximum priority.

The last part of this program demonstrates methods for an entire group of threads. First the program moves through the entire tree of threads and starts each one that hasn’t been started. For drama, the system group is then suspended and finally stopped. (Although it’s interesting to see that suspend( ) and stop( ) work on entire thread groups, you should keep in mind that these methods are deprecated in Java 2.) But when you suspend the system group you also suspend the main thread and the whole program shuts down, so it never gets to the point where the threads are stopped. Actually, if you do stop the main thread it throws a ThreadDeath exception, so this is not a typical thing to do. Since ThreadGroup is inherited from Object, which contains the wait( ) method, you can also choose to suspend the program for any number of seconds by calling wait(seconds * 1000). This must acquire the lock inside a synchronized block, of course.

The ThreadGroup class also has suspend( ) and resume( ) methods so you can stop and start an entire thread group and all of its threads and subgroups with a single command. (Again, suspend( ) and resume( ) are deprecated in Java 2.)

Thread groups can seem a bit mysterious at first, but keep in mind that you probably won’t be using them directly very often.

Runnable revisited

Earlier in this chapter, I suggested that you think carefully before making an applet or main Frame as an implementation of Runnable. Of course, if you must inherit from a class and you want to add threading behavior to the class, Runnable is the correct solution. The final example in this chapter exploits this by making a Runnable JPanel class that paints different colors on itself. This application is set up to take values from the command line to determine how big the grid of colors is and how long to sleep( ) between color changes. By playing with these values you’ll discover some interesting and possibly inexplicable features of threads:

//: c14:ColorBoxes.java
// Using the Runnable interface.
// <applet code=ColorBoxes width=500 height=400>
// <param name=grid value="12">
// <param name=pause value="50">
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

class CBox extends JPanel implements Runnable {
  private Thread t;
  private int pause;
  private static final Color[] colors = { 
    Color.black, Color.blue, Color.cyan, 
    Color.darkGray, Color.gray, Color.green,
    Color.lightGray, Color.magenta, 
    Color.orange, Color.pink, Color.red, 
    Color.white, Color.yellow 
  };
  private Color cColor = newColor();
  private static final Color newColor() {
    return colors[
      (int)(Math.random() * colors.length)
    ];
  }
  public void paintComponent(Graphics  g) {
    super.paintComponent(g);
    g.setColor(cColor);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
  }
  public CBox(int pause) {
    this.pause = pause;
    t = new Thread(this);
    t.start(); 
  }
  public void run() {
    while(true) {
      cColor = newColor();
      repaint();
      try {
        t.sleep(pause);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
    } 
  }
} 

public class ColorBoxes extends JApplet {
  private boolean isApplet = true;
  private int grid = 12;
  private int pause = 50;
  public void init() {
    // Get parameters from Web page:
    if (isApplet) {
      String gsize = getParameter("grid");
      if(gsize != null)
        grid = Integer.parseInt(gsize);
      String pse = getParameter("pause");
      if(pse != null)
        pause = Integer.parseInt(pse);
    }
    Container cp = getContentPane();
    cp.setLayout(new GridLayout(grid, grid));
    for (int i = 0; i < grid * grid; i++)
      cp.add(new CBox(pause));
  }
  public static void main(String[] args) {
    ColorBoxes applet = new ColorBoxes();
    applet.isApplet = false;
    if(args.length > 0)
      applet.grid = Integer.parseInt(args[0]);
    if(args.length > 1) 
      applet.pause = Integer.parseInt(args[1]);
    Console.run(applet, 500, 400);
  }
} ///:~

ColorBoxes is the usual applet/application with an init( ) that sets up the GUI. This sets up the GridLayout so that it has grid cells in each dimension. Then it adds the appropriate number of CBox objects to fill the grid, passing the pause value to each one. In main( ) you can see how pause and grid have default values that can be changed if you pass in command-line arguments, or by using applet parameters.

CBox is where all the work takes place. This is inherited from JPanel and it implements the Runnable interface so each JPanel can also be a Thread. Remember that when you implement Runnable, you don’t make a Thread object, just a class that has a run( ) method. Thus, you must explicitly create a Thread object and hand the Runnable object to the constructor, then call start( ) (this happens in the constructor). In CBox this thread is called t.

Notice the array colors, which is an enumeration of all the colors in class Color. This is used in newColor( ) to produce a randomly selected color. The current cell color is cColor.

paintComponent( ) is quite simple—it just sets the color to cColor and fills the entire JPanel with that color.

In run( ), you see the infinite loop that sets the cColor to a new random color and then calls repaint( ) to show it. Then the thread goes to sleep( ) for the amount of time specified on the command line.

Precisely because this design is flexible and threading is tied to each JPanel element, you can experiment by making as many threads as you want. (In reality, there is a restriction imposed by the number of threads your JVM can comfortably handle.)

This program also makes an interesting benchmark, since it can show dramatic performance differences between one JVM threading implementation and another.

Too many threads

At some point, you’ll find that ColorBoxes bogs down. On my machine, this occurred somewhere after a 10 x 10 grid. Why does this happen? You’re naturally suspicious that Swing might have something to do with it, so here’s an example that tests that premise by making fewer threads. The following code is reorganized so that an ArrayList implements Runnable and that ArrayList holds a number of color blocks and randomly chooses ones to update. Then a number of these ArrayList objects are created, depending roughly on the grid dimension you choose. As a result, you have far fewer threads than color blocks, so if there’s a speedup we’ll know it was because there were too many threads in the previous example:

//: c14:ColorBoxes2.java
// Balancing thread use.
// <applet code=ColorBoxes2 width=600 height=500>
// <param name=grid value="12">
// <param name=pause value="50">
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;

class CBox2 extends JPanel {
  private static final Color[] colors = { 
    Color.black, Color.blue, Color.cyan, 
    Color.darkGray, Color.gray, Color.green,
    Color.lightGray, Color.magenta, 
    Color.orange, Color.pink, Color.red, 
    Color.white, Color.yellow 
  };
  private Color cColor = newColor();
  private static final Color newColor() {
    return colors[
      (int)(Math.random() * colors.length)
    ];
  }
  void nextColor() {
    cColor = newColor();
    repaint();
  }
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(cColor);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
  }
}

class CBoxList 
  extends ArrayList implements Runnable {
  private Thread t;
  private int pause;
  public CBoxList(int pause) {
    this.pause = pause;
    t = new Thread(this);
  }
  public void go() { t.start(); }
  public void run() {
    while(true) {
      int i = (int)(Math.random() * size());
      ((CBox2)get(i)).nextColor();
      try {
        t.sleep(pause);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
    } 
  }
  public Object last() { return get(size() - 1);}
}

public class ColorBoxes2 extends JApplet {
  private boolean isApplet = true;
  private int grid = 12;
  // Shorter default pause than ColorBoxes:
  private int pause = 50;
  private CBoxList[] v;
  public void init() {
    // Get parameters from Web page:
    if (isApplet) {
      String gsize = getParameter("grid");
      if(gsize != null)
        grid = Integer.parseInt(gsize);
      String pse = getParameter("pause");
      if(pse != null)
        pause = Integer.parseInt(pse);
    }
    Container cp = getContentPane();
    cp.setLayout(new GridLayout(grid, grid));
    v = new CBoxList[grid];
    for(int i = 0; i < grid; i++)
      v[i] = new CBoxList(pause);
    for (int i = 0; i < grid * grid; i++) {
      v[i % grid].add(new CBox2());
      cp.add((CBox2)v[i % grid].last());
    }
    for(int i = 0; i < grid; i++)
      v[i].go();
  }   
  public static void main(String[] args) {
    ColorBoxes2 applet = new ColorBoxes2();
    applet.isApplet = false;
    if(args.length > 0)
      applet.grid = Integer.parseInt(args[0]);
    if(args.length > 1) 
      applet.pause = Integer.parseInt(args[1]);
    Console.run(applet, 500, 400);
  }
} ///:~

In ColorBoxes2 an array of CBoxList is created and initialized to hold grid CBoxLists, each of which knows how long to sleep. An equal number of CBox2 objects is then added to each CBoxList, and each list is told to go( ), which starts its thread.

CBox2 is similar to CBox: it paints itself with a randomly chosen color. But that’s all a CBox2 does. All of the threading has been moved into CBoxList.

The CBoxList could also have inherited Thread and had a member object of type ArrayList. That design has the advantage that the add( ) and get( ) methods could then be given specific argument and return value types instead of generic Objects. (Their names could also be changed to something shorter.) However, the design used here seemed at first glance to require less code. In addition, it automatically retains all the other behaviors of an ArrayList. With all the casting and parentheses necessary for get( ), this might not be the case as your body of code grows.

As before, when you implement Runnable you don’t get all of the equipment that comes with Thread, so you have to create a new Thread and hand yourself to its constructor in order to have something to start( ), as you can see in the CBoxList constructor and in go( ). The run( ) method simply chooses a random element number within the list and calls nextColor( ) for that element to cause it to choose a new randomly selected color.

Upon running this program, you see that it does indeed run faster and respond more quickly (for instance, when you interrupt it, it stops more quickly), and it doesn’t seem to bog down as much at higher grid sizes. Thus, a new factor is added into the threading equation: you must watch to see that you don’t have “too many threads” (whatever that turns out to mean for your particular program and platform—here, the slowdown in ColorBoxes appears to be caused by the fact that there’s only one thread that is responsible for all painting, and it gets bogged down by too many requests). If you have too many threads, you must try to use techniques like the one above to “balance” the number of threads in your program. If you see performance problems in a multithreaded program you now have a number of issues to examine:

  1. Do you have enough calls to sleep( ), yield( ), and/or wait( )?
  2. Are calls to sleep( ) long enough?
  3. Are you running too many threads?
  4. Have you tried different platforms and JVMs?

Issues like this are one reason that multithreaded programming is often considered an art.

Summary

It is vital to learn when to use multithreading and when to avoid it. The main reason to use it is to manage a number of tasks whose intermingling will make more efficient use of the computer (including the ability to transparently distribute the tasks across multiple CPUs) or be more convenient for the user. The classic example of resource balancing is using the CPU during I/O waits. The classic example of user convenience is monitoring a “stop” button during long downloads.

The main drawbacks to multithreading are:

  1. Slowdown while waiting for shared resources
  2. Additional CPU overhead required to manage threads
  3. Unrewarded complexity, such as the silly idea of having a separate thread to update each element of an array
  4. Pathologies including starving, racing, and deadlock

An additional advantage to threads is that they substitute “light” execution context switches (of the order of 100 instructions) for “heavy” process context switches (of the order of 1000s of instructions). Since all threads in a given process share the same memory space, a light context switch changes only program execution and local variables. On the other hand, a process change—the heavy context switch—must exchange the full memory space.

Threading is like stepping into an entirely new world and learning a whole new programming language, or at least a new set of language concepts. With the appearance of thread support in most microcomputer operating systems, extensions for threads have also been appearing in programming languages or libraries. In all cases, thread programming (1) seems mysterious and requires a shift in the way you think about programming; and (2) looks similar to thread support in other languages, so when you understand threads, you understand a common tongue. And although support for threads can make Java seem like a more complicated language, don’t blame Java. Threads are tricky.

One of the biggest difficulties with threads occurs because more than one thread might be sharing a resource—such as the memory in an object—and you must make sure that multiple threads don’t try to read and change that resource at the same time. This requires judicious use of the synchronized keyword, which is a helpful tool but must be understood thoroughly because it can quietly introduce deadlock situations.

In addition, there’s a certain art to the application of threads. Java is designed to allow you to create as many objects as you need to solve your problem—at least in theory. (Creating millions of objects for an engineering finite-element analysis, for example, might not be practical in Java.) However, it seems that there is an upper bound to the number of threads you’ll want to create, because at some point a large number of threads seems to become unwieldy. This critical point is not in the many thousands as it might be with objects, but rather in the low hundreds, sometimes less than 100. As you often create only a handful of threads to solve a problem, this is typically not much of a limit, yet in a more general design it becomes a constraint.

A significant nonintuitive issue in threading is that, because of thread scheduling, you can typically make your applications run faster by inserting calls to sleep( ) inside run( )’s main loop. This definitely makes it feel like an art, in particular when the longer delays seem to speed up performance. Of course, the reason this happens is that shorter delays can cause the end-of-sleep( ) scheduler interrupt to happen before the running thread is ready to go to sleep, forcing the scheduler to stop it and restart it later so it can finish what it was doing and then go to sleep. It takes extra thought to realize how messy things can get.

One thing you might notice missing in this chapter is an animation example, which is one of the most popular things to do with applets. However, a complete solution (with sound) to this problem comes with the Java JDK (available at java.sun.com) in the demo section. In addition, we can expect better animation support to become part of future versions of Java, while completely different non-Java, non-programming solutions to animation for the Web are appearing that will probably be superior to traditional approaches. For explanations about how Java animation works, see Core Java 2 by Horstmann & Cornell, Prentice-Hall, 1997. For more advanced discussions of threading, see Concurrent Programming in Java by Doug Lea, Addison-Wesley, 1997, or Java Threads by Oaks & Wong, O’Reilly, 1997.

Exercises

Solutions to selected exercises can be found in the electronic document The Thinking in Java Annotated Solution Guide, available for a small fee from www.BruceEckel.com.

  1. Inherit a class from Thread and override the run( ) method. Inside run( ), print a message, and then call sleep( ). Repeat this three times, then return from run( ). Put a start-up message in the constructor and override finalize( ) to print a shut-down message. Make a separate thread class that calls System.gc( ) and System.runFinalization( ) inside run( ), printing a message as it does so. Make several thread objects of both types and run them to see what happens.
  2. Modify Sharing2.java to add a synchronized block inside the run( ) method of TwoCounter instead of synchronizing the entire run( ) method.
  3. Create two Thread subclasses, one with a run( ) that starts up, captures the reference of the second Thread object and then calls wait( ). The other class’ run( ) should call notifyAll( ) for the first thread after some number of seconds have passed, so the first thread can print a message.
  4. In Counter5.java inside Ticker2, remove the yield( ) and explain the results. Replace the yield( ) with a sleep( ) and explain the results.
  5. In ThreadGroup1.java, replace the call to sys.suspend( ) with a call to wait( ) for the thread group, causing it to wait for two seconds. For this to work correctly you must acquire the lock for sys inside a synchronized block.
  6. Change Daemons.java so that main( ) has a sleep( ) instead of a readLine( ). Experiment with different sleep times to see what happens.
  7. In Chapter 8, locate the GreenhouseControls.java example, which consists of three files. In Event.java, the class Event is based on watching the time. Change Event so that it is a Thread, and change the rest of the design so that it works with this new Thread-based Event.
  8. Modify Exercise 7 so that the java.util.Timer class found in JDK 1.3 is used to run the system.
  9. Starting with SineWave.java from Chapter 13, create a program (an applet/application using the Console class) that draws an animated sine wave that appears to scrolls past the viewing window like an oscilloscope, driving the animation with a Thread. The speed of the animation should be controlled with a java.swing.JSlider control.
  10. Modify Exercise 9 so that multiple sine wave panels are created within the application. The number of sine wave panels should be controlled by HTML tags or command-line parameters.
  11. Modify Exercise 9 so that the java.swing.Timer class is used to drive the animation. Note the difference between this and java.util.Timer.

[70] 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.

[71] The Java Programming Language, by Ken Arnold and James Gosling, Addison-Wesley 1996 pp 179.

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]
Last Update:04/24/2000