MindView Inc.

Thinking in Java, 3rd ed. Revision 2.0


[ Viewing Hints ] [ Book Home Page ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Previous Next Title Page Index Contents

13: Concurrency

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. Comment

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

There are many possible uses for multithreading, but in general, 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. Comment

Understanding concurrent programming is on the same order of difficulty as understanding polymorphism. If you apply some effort, you can fathom the basic mechanism, but it generally takes deep study and understanding in order to develop a true grasp of the subject. The goal of this chapter is to give you a solid foundation in the basics of concurrency, so that you can understand the concepts and write reasonable multithreaded programs. Be aware that you can easily become overconfident, so if you are writing anything complex you will need to study dedicated books on the topic. Comment

Motivation

A good example of the application of concurrency 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. Comment

As a starting point, consider a program that performs some CPU-intensive operation and thus ends up ignoring user input and being unresponsive. The basic problem here is that the program needs to continue performing its operations, and at the same time it needs to return control to the user interface so that the program can respond to the user. But a conventional method 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 concurrency provides. Comment

The above example addresses the use of concurrency to optimize throughput. IO is another place where lack of throughput can clog up the flow of your program: you might be able to do important work while you’re stuck waiting for input to arrive, for example.Comment

Code organization (separate thread management from user code), modularity

Simulation

Loosely-coupled design

No polling

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. Comment

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

Basic threads

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

The following example creates five 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 return when the count is zero (at the point when run( ) returns, the thread is terminated by the threading mechanism). Comment

//: c14:SimpleThread.java
// Very simple Threading example.
import com.bruceeckel.simpletest.*;

public class SimpleThread extends Thread {
  static Test monitor = new Test();
  private int countDown = 5;
  private static int threadCount = 0;
  public SimpleThread() {
    super("" + ++threadCount); // Store the thread name
    start();
  }
  public String toString() {
    return "#" + getName() + ": " + countDown;
  }
  public void run() {
    while(true) {
      System.out.println(this);
      if(--countDown == 0) return;
    }
  }
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++)
      new SimpleThread();
    monitor.expect(new String[] {
      "#1: 5",
      "#2: 5",
      "#3: 5",
      "#5: 5",
      "#1: 4",
      "#4: 5",
      "#2: 4",
      "#3: 4",
      "#5: 4",
      "#1: 3",
      "#4: 4",
      "#2: 3",
      "#3: 3",
      "#5: 3",
      "#1: 2",
      "#4: 3",
      "#2: 2",
      "#3: 2",
      "#5: 2",
      "#1: 1",
      "#4: 2",
      "#2: 1",
      "#3: 1",
      "#5: 1",
      "#4: 1"
    }, Test.IGNORE_ORDER + Test.DELAY_SHORT);
  }
} ///:~


The thread objects are given specific names by calling the appropriate Thread constructor; this name is then retrieved using getName().

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 factor that causes run( ) to terminate, it will continue forever. Comment

In main( ) you can see a number of threads being created and run. The start( ) method in the Thread class performs special initialization for the thread and then calls run( ). So the steps are: the constructor is called to build the object, it calls start( ) to configure the thread and the thread execution mechanism calls run( ). If you don’t call start( ) (which you don’t have to do in the constructor, as you will see in subsequent examples) the thread will never be started. Comment

The output for one run of this program will be different from that of another, because the thread scheduling mechanism is not deterministic. In fact, you may see dramatic differences in the output of this simple program between one version of the JDK and the next. For example, a previous JDK didn’t time-slice very often, so thread 1 might loop to extinction first, then thread 2 would go through all of its loops, etc. This was virtually the same as calling a routine that would do all the loops at once, except that starting up all those threads is significantly more expensive. In JDK 1.4 you get something like the above output, which indicates better time-slicing behavior by the scheduler – each thread seems to be getting regular service. Generally these kinds of JDK behavioral changes have not been mentioned by Sun, so you cannot plan on any consistent threading behavior. The best approach is to be as conservative as possible while writing threading code. Comment

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

Yeilding

If you know that you’ve accomplished what you need to in your run() method, you can give a hint to the thread scheduling mechanism that you’ve done enough and that some other thread might as well have the CPU. This hint (and it is a hint – there’s no guarantee your implementation will listen to it) takes the form of the yield() method. Comment

We can modify the above example by yielding after each loop:

//: c14:YieldingThread.java
// Suggesting when to switch threads with yield().
import com.bruceeckel.simpletest.*;

public class YieldingThread extends Thread {
  static Test monitor = new Test();
  private int countDown = 5;
  private static int threadCount = 0;
  public YieldingThread() {
    super("" + ++threadCount); // Store the thread name
    start();
  }
  public String toString() {
    return "#" + getName() + ": " + countDown;
  }
  public void run() {
    while(true) {
      System.out.println(this);
      if(--countDown == 0) return;
      yield();
    }
  }
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++)
      new YieldingThread();
    monitor.expect(new String[] {
      "#1: 5",
      "#2: 5",
      "#4: 5",
      "#5: 5",
      "#3: 5",
      "#1: 4",
      "#2: 4",
      "#4: 4",
      "#5: 4",
      "#3: 4",
      "#1: 3",
      "#2: 3",
      "#4: 3",
      "#5: 3",
      "#3: 3",
      "#1: 2",
      "#2: 2",
      "#4: 2",
      "#5: 2",
      "#3: 2",
      "#1: 1",
      "#2: 1",
      "#4: 1",
      "#5: 1",
      "#3: 1"
    }, Test.IGNORE_ORDER + Test.DELAY_SHORT);
  }
} ///:~


By using yield(), the output is evened up quite a bit. But note that if the output string is longer, you will see output that is roughly the same as it was in SimpleThread.java (try it – change toString() to put out longer and longer strings to see what happens). Since the scheduling mechanism is preemptive, it decides to interrupt a thread and switch to another whenever it wants, so if IO (which is executed via the main() thread) takes too long it gets interrupted before run() has a chance to yield(). In general, yield() is useful only in rare situations and you can’t rely on it to do any serious tuning of your application. Comment

Sleeping

Another way you can control the behavior of your threads is by calling sleep() to cease execution for a given number of milliseconds. If you replace the call to yield() in the above example with a call to sleep(), you get the following: Comment

//: c14:SleepingThread.java
// Calling sleep() to wait for awhile.
import com.bruceeckel.simpletest.*;

public class SleepingThread extends Thread {
  static Test monitor = new Test();
  private int countDown = 5;
  private static int threadCount = 0;
  public SleepingThread() {
    super("" + ++threadCount); // Store the thread name
    start();
  }
  public String toString() {
    return "#" + getName() + ": " + countDown;
  }
  public void run() {
    while(true) {
      System.out.println(this);
      if(--countDown == 0) return;
      try {
        sleep(100);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }
  }
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++)
      new SleepingThread();
    monitor.expect(new String[] {
      "#1: 5",
      "#2: 5",
      "#4: 5",
      "#5: 5",
      "#3: 5",
      "#1: 4",
      "#2: 4",
      "#4: 4",
      "#5: 4",
      "#3: 4",
      "#1: 3",
      "#2: 3",
      "#4: 3",
      "#5: 3",
      "#3: 3",
      "#1: 2",
      "#2: 2",
      "#4: 2",
      "#5: 2",
      "#3: 2",
      "#1: 1",
      "#2: 1",
      "#4: 1",
      "#5: 1",
      "#3: 1"
    }, Test.IGNORE_ORDER + Test.DELAY_SHORT);
  }
} ///:~


When you call sleep(), it must be placed inside a try block because it’s possible for sleep() to be interrupted before it times out. This happens if someone else has a reference to the thread and they call interrupt() on the thread (interrupt() also affects the thread if wait() or join() has been called for it, so those calls must be in a similar try block – you’ll learn about those methods later). Usually, if you’re going to break out of a suspended thread using interrupt() you will use wait() rather than sleep(), so ending up inside of the catch clause is unlikely. Here, we follow the maxim “don’t catch an exception unless you know what to do with it” by re-throwing it as a RuntimeException. Comment

You’ll notice that the output is similar to yield(), which means that sleep() is also not a way for you to control the order of thread execution. It just stops the execution of the thread for awhile. The only guarantee that you have is that the thread will sleep at least 100 milliseconds, but it may take longer before the thread resumes execution because the thread scheduler still has to get back to it after the sleep interval expires. Comment

If you must control the order of execution of threads, your best bet is not to use threads at all but instead to write your own cooperative routines which hand control to each other in a specified order. Comment

Priority

The priority of a thread tells the scheduler how important this thread is. Although the order that the CPU attends to an existing set of threads is indeterminate, if there are a number of threads blocked and waiting to be run, the scheduler will lean towards 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. Comment

Here’s SimpleThread.java modified so that the priority levels are demonstrated. The priorities are adjusting using Thread’s setPriority( ) method.

//: c14:SimplePriorities.java
// Shows the use of thread priorities.
import com.bruceeckel.simpletest.*;

public class SimplePriorities extends Thread {
  static Test monitor = new Test();
  private int countDown = 5;
  private volatile double d = 0; // No optimization
  public SimplePriorities(int priority) {
    setPriority(priority);
    start();
  }
  public String toString() {
    return super.toString() + ": " + countDown;
  }
  public void run() {
    while(true) {
      // An expensive, interruptable operation:
      for(int i = 1; i < 100000; i++)
        d = d + (Math.PI + Math.E) / (double)i;
      System.out.println(this);
      if(--countDown == 0) return;
    }
  }
  public static void main(String[] args) {
    new SimplePriorities(Thread.MAX_PRIORITY);
    for(int i = 0; i < 5; i++)
      new SimplePriorities(Thread.MIN_PRIORITY);
    monitor.expect(new String[] {
      "Thread[Thread-1,10,main]: 5",
      "Thread[Thread-5,1,main]: 5",
      "Thread[Thread-1,10,main]: 4",
      "Thread[Thread-1,10,main]: 3",
      "Thread[Thread-2,1,main]: 5",
      "Thread[Thread-1,10,main]: 2",
      "Thread[Thread-2,1,main]: 4",
      "Thread[Thread-1,10,main]: 1",
      "Thread[Thread-2,1,main]: 3",
      "Thread[Thread-4,1,main]: 5",
      "Thread[Thread-6,1,main]: 5",
      "Thread[Thread-4,1,main]: 4",
      "Thread[Thread-6,1,main]: 4",
      "Thread[Thread-4,1,main]: 3",
      "Thread[Thread-6,1,main]: 3",
      "Thread[Thread-4,1,main]: 2",
      "Thread[Thread-6,1,main]: 2",
      "Thread[Thread-4,1,main]: 1",
      "Thread[Thread-5,1,main]: 4",
      "Thread[Thread-6,1,main]: 1",
      "Thread[Thread-2,1,main]: 2",
      "Thread[Thread-5,1,main]: 3",
      "Thread[Thread-2,1,main]: 1",
      "Thread[Thread-3,1,main]: 5",
      "Thread[Thread-5,1,main]: 2",
      "Thread[Thread-3,1,main]: 4",
      "Thread[Thread-5,1,main]: 1",
      "Thread[Thread-3,1,main]: 3",
      "Thread[Thread-3,1,main]: 2",
      "Thread[Thread-3,1,main]: 1"
    }, Test.IGNORE_ORDER + Test.DELAY_SHORT);
  }
} ///:~


In this version, toString() is overridden to use the default version for Thread, which prints the thread name (which you can set yourself via the constructor; here it’s automatically generated as Thread-1, Thread-2, etc.), the priority level, and the “thread group” that the thread belongs to. Because the threads are self-identifying, there is no threadNumber in this example. The overridden toString() also shows the countdown value of the thread. Comment

You can see that the priority level of thread #1 is at the highest level, and all the rest of the threads are at the lowest level. Comment

Inside run(), 10,000 repetitions of a rather expensive floating-point calculation have been added, involving double addition and division. The variable d has been made volatile to ensure that no optimization is performed. Without this calculation, you don’t see the effect of setting the priority levels (try it: comment out the for loop containing the double calculations). With the calculation, you see that thread #1 is given a higher preference by the thread scheduler (at least, this was the behavior on my Windows 2000 machine). Even though printing to the console is also an expensive behavior, you won’t see the priority levels that way because console printing doesn’t get interrupted (otherwise the console display would get garbled during threading), whereas the math calculation above can be interrupted. The calculation takes long enough that the thread scheduling mechanism jumps in and changes threads, and pays attention to the priorities so that thread 1 gets preference. Comment

You can also read the priority of an existing thread with getPriority( ) and change it at any time (not just in the constructor, as above) with setPriority( ).

Although the JDK has 10 priority levels, this doesn’t map well to many operating systems. For example, Windows 2000 has 7 priority levels which are not fixed, so the mapping is indeterminate (although Sun’s Solaris has 231 levels). The only portable approach is to stick to MAX_PRIORITY, NORM_PRIORITY and MIN_PRIORITY when you’re adjusting priority levels. Comment

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 non-daemon thread that runs main( ). Comment

//: c14:SimpleDaemons.java
// Daemon threads don't prevent the program from ending.

public class SimpleDaemons extends Thread {
  public SimpleDaemons() {
    setDaemon(true); // Must be called before start()
    start(); 
  }
  public void run() {
    while(true) {
      try {
        sleep(100);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
      System.out.println(this);
    }
  }
  public static void main(String[] args) {
    for(int i = 0; i < 10; i++)
      new SimpleDaemons();
  }
} ///:~


You must set the thread to be a daemon by calling setDaemon( ) before it is started. In run(), the thread is put to sleep for a little bit. Once the threads are all started, the program terminates immediately, before any threads can print themselves, because there are no non-daemon threads (other than main()) holding the program open. Thus, the program terminates without printing any output.

You can find out if a thread is a daemon by calling isDaemon( ). If a thread is a daemon, then any threads it creates will automatically be daemons, as the following example demonstrates: Comment

//: c14:Daemons.java
// Daemon threads spawn other daemon threads.
import java.io.*;
import com.bruceeckel.simpletest.*;

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

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

public class Daemons {
  static Test monitor = new Test();
  public static void main(String[] args) throws Exception {
    Thread d = new Daemon();
    System.out.println("d.isDaemon() = " + d.isDaemon());
    // Allow the daemon threads to
    // finish their startup processes:
    Thread.sleep(1000);
    monitor.expect(new String[] {
      "d.isDaemon() = true",
      "DaemonSpawn 0 started",
      "DaemonSpawn 1 started",
      "DaemonSpawn 2 started",
      "DaemonSpawn 3 started",
      "DaemonSpawn 4 started",
      "DaemonSpawn 5 started",
      "DaemonSpawn 6 started",
      "DaemonSpawn 7 started",
      "DaemonSpawn 8 started",
      "DaemonSpawn 9 started",
      "t[0].isDaemon() = true",
      "t[1].isDaemon() = true",
      "t[2].isDaemon() = true",
      "t[3].isDaemon() = true",
      "t[4].isDaemon() = true",
      "t[5].isDaemon() = true",
      "t[6].isDaemon() = true",
      "t[7].isDaemon() = true",
      "t[8].isDaemon() = true",
      "t[9].isDaemon() = true"
    });
  }
} ///:~


The Daemon thread sets its daemon flag to “true” and then spawns a bunch of other threads – which do not set themselves to daemon mode – to show that they are daemons anyway. Then it goes into an infinite loop that calls yield( ) to give up control to the other processes. Comment

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, the main() thread is put to sleep for a second. Without this you see only some of the results from the creation of the daemon threads. (Try sleep( ) calls of various lengths to see this behavior.) Comment

Joining a thread

One thread may call join() on another thread to wait for the second thread to complete before proceeding. If a thread calls t.join() on another thread t, then the calling thread is suspended until the target thread t finishes. This happens when t.isAlive() is false.

You may also call join() with a timeout argument (in either milliseconds or milliseconds and nanoseconds) so that if the target thread doesn’t finish in that period of time the call to join() returns anyway.

The call to join() may be aborted by calling interrupt() on the calling thread, so a try-catch clause is required.

All of these operations are shown in the following example:

//: c14:Joining.java
// Understanding join().
import com.bruceeckel.simpletest.*;

class Sleeper extends Thread {
  private int duration;
  public Sleeper(String name, int sleepTime) {
    super(name);
    duration = sleepTime;
    start();
  }
  public void run() {
    try {
      sleep(duration);
    } catch (InterruptedException e) {
      System.out.println(getName() + " was interrupted. " +
        "isInterrupted(): " + isInterrupted());
      return;
    }
    System.out.println(getName() + " has awakened");
  }
}

class Joiner extends Thread {
  private Sleeper sleeper;
  public Joiner(String name, Sleeper sleeper) {
    super(name);
    this.sleeper = sleeper;
    start();
  }
  public void run() {
   try {
      sleeper.join();
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    System.out.println(getName() + " join completed");
  }
}

public class Joining {
  static Test monitor = new Test();
  public static void main(String[] args) {
    Sleeper
      sleepy = new Sleeper("Sleepy", 1500),
      grumpy = new Sleeper("Grumpy", 1500);
    Joiner
      dopey = new Joiner("Dopey", sleepy),
      doc = new Joiner("Doc", grumpy);
    grumpy.interrupt();
    monitor.expect(new String[] {
      "Grumpy was interrupted. isInterrupted(): false",
      "Doc join completed",
      "Sleepy has awakened",
      "Dopey join completed"
    }, Test.AT_LEAST + Test.DELAY_MEDIUM);
  }
} ///:~


A Sleeper is a type of Thread that goes to sleep for a time specified in its constructor. In run(), the call to sleep() may terminate when the time expires, but it may also be interrupted. Inside the catch clause, the interruption is reported, along with the value of isInterrupted(). When another thread calls interrupt() on this thread, a flag is set to indicate that the thread has been interrupted. However, this flag is cleared when the exception is caught, so the result will always be false inside the catch clause. The flag is used for other situations where a thread may examine its interrupted state apart from the exception.

A Joiner is a thread that waits for a Sleeper to wake up by calling join() on the Sleeper object. In main(), each Sleeper has a Joiner, and you can see in the output that if the Sleeper is either interrupted or if it ends normally, the Joiner completes in conjunction with the Sleeper.

Coding variations

In the simple examples above, the thread objects are all inherited from Thread. This makes sense because the objects are clearly only being created as threads, and have no other behavior. However, your class may already be inheriting from another class, in which case you can’t also inherit from Thread (Java doesn’t support multiple inheritance). In this case, you can use the alternative approach of implementing the Runnable interface. Runnable specifies only that there be a run( ) method implemented, and Thread also implements Runnable. Comment

This example demonstrates the basics:

//: c14:RunnableThread.java
// SimpleThread using the Runnable interface.
import com.bruceeckel.simpletest.*;

public class RunnableThread implements Runnable {
  static Test monitor = new Test();
  private int countDown = 5;
  public String toString() {
    return "#" + Thread.currentThread().getName() +
      ": " + countDown;
  }
  public void run() {
    while(true) {
      System.out.println(this);
      if(--countDown == 0) return;
    }
  }
  public static void main(String[] args) {
    for(int i = 1; i <= 5; i++)
      new Thread(new RunnableThread(), "" + i).start();
    monitor.expect(new String[] {
      "#1: 5",
      "#2: 5",
      "#3: 5",
      "#5: 5",
      "#1: 4",
      "#4: 5",
      "#2: 4",
      "#3: 4",
      "#5: 4",
      "#1: 3",
      "#4: 4",
      "#2: 3",
      "#3: 3",
      "#5: 3",
      "#1: 2",
      "#4: 3",
      "#2: 2",
      "#3: 2",
      "#5: 2",
      "#1: 1",
      "#4: 2",
      "#2: 1",
      "#3: 1",
      "#5: 1",
      "#4: 1"
    }, Test.IGNORE_ORDER + Test.DELAY_SHORT);
  }
} ///:~


The only thing required by a Runnable class is a run() method, but if you want do do anything else to the Thread object (such as getName() in toString()) you must explicitly get a reference to it by calling Thread.currentThread(). This particular Thread constructor takes a Runnable and a name for the thread.

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, which performs the usual initialization and then calls run( ). Comment

The convenient aspect about the Runnable interface is that everything belongs to the same class; that is, Runnable allows a mixin with a base class and other interfaces. If you need to access something, you simply do it without going through a separate object. However, inner classes have this same easy access to all the parts of an outer class, so member access is not a compelling reason to use Runnable as a mixin rather than an inner subclass of Thread. Comment

When you use Runnable, you’re generally saying that you want to create a process in a piece of code – implemented in the run() method – rather than an object representing that process. This is a matter of some debate, depending on whether you feel it makes more sense to represent a thread as an object or as a completely different entity, a process[63]. If you choose to think of it as a process, then you are freed from the object-oriented imperative that “everything is an object.” This also means that there’s no reason to make your whole class Runnable if you only want to start a process to drive some part of your program. Because of this, it often makes more sense to hide your threading code inside inside your class, using an inner class, as shown here:

//: c14:ThreadVariations.java
// Creating threads with inner classes.
import com.bruceeckel.simpletest.*;

// Using a named inner class:
class InnerThread1 {
  private int countDown = 5;
  private Inner inner;
  private class Inner extends Thread {
    Inner(String name) {
      super(name);
      start();
    }
    public void run() {
      while(true) {
        System.out.println(this);
        if(--countDown == 0) return;
        try {
          sleep(10);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
    }
    public String toString() {
      return getName() + ": " + countDown;
    }
  }
  public InnerThread1(String name) {
    inner = new Inner(name);
  }
}

// Using an anonymous inner class:
class InnerThread2 {
  private int countDown = 5;
  private Thread t;
  public InnerThread2(String name) {
    t = new Thread(name) {
      public void run() {
        while(true) {
          System.out.println(this);
          if(--countDown == 0) return;
          try {
            sleep(10);
          } catch (InterruptedException e) {
            throw new RuntimeException(e);
          }
        }
      }
      public String toString() {
        return getName() + ": " + countDown;
      }
    };
    t.start();
  }
}

// Using a named Runnable implementation:
class InnerRunnable1 {
  private int countDown = 5;
  private Inner inner;
  private class Inner implements Runnable {
    Thread t;
    Inner(String name) {
      t = new Thread(this, name);
      t.start();
    }
    public void run() {
      while(true) {
        System.out.println(this);
        if(--countDown == 0) return;
        try {
          Thread.currentThread().sleep(10);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
    }
    public String toString() {
      return t.getName() + ": " + countDown;
    }
  }
  public InnerRunnable1(String name) {
    inner = new Inner(name);
  }
}

// Using an anonymous Runnable implementation:
class InnerRunnable2 {
  private int countDown = 5;
  private Thread t;
  public InnerRunnable2(String name) {
    t = new Thread(new Runnable() {
      public void run() {
        while(true) {
          System.out.println(this);
          if(--countDown == 0) return;
          try {
            Thread.currentThread().sleep(10);
          } catch (InterruptedException e) {
            throw new RuntimeException(e);
          }
        }
      }
      public String toString() {
        return Thread.currentThread().getName() +
          ": " + countDown;
      }
    }, name);
    t.start();
  }
}

// A separate method to run some code as a thread:
class ThreadMethod {
  private int countDown = 5;
  private Thread t;
  private String name;
  public ThreadMethod(String name) { this.name = name; }
  public void runThread() {
    if(t == null) {
      t = new Thread(name) {
        public void run() {
          while(true) {
            System.out.println(this);
            if(--countDown == 0) return;
            try {
              sleep(10);
            } catch (InterruptedException e) {
              throw new RuntimeException(e);
            }
          }
        }
        public String toString() {
          return getName() + ": " + countDown;
        }
      };
      t.start();
    }
  }
}

public class ThreadVariations {
  static Test monitor = new Test();
  public static void main(String[] args) {
    new InnerThread1("InnerThread1");
    new InnerThread2("InnerThread2");
    new InnerRunnable1("InnerRunnable1");
    new InnerRunnable2("InnerRunnable2");
    new ThreadMethod("ThreadMethod").runThread();
    monitor.expect(new String[] {
      "InnerThread1: 5",
      "InnerThread2: 5",
      "InnerThread2: 4",
      "InnerRunnable1: 5",
      "InnerThread1: 4",
      "InnerRunnable2: 5",
      "ThreadMethod: 5",
      "InnerRunnable1: 4",
      "InnerThread2: 3",
      "InnerRunnable2: 4",
      "ThreadMethod: 4",
      "InnerThread1: 3",
      "InnerRunnable1: 3",
      "ThreadMethod: 3",
      "InnerThread1: 2",
      "InnerThread2: 2",
      "InnerRunnable2: 3",
      "InnerThread2: 1",
      "InnerRunnable2: 2",
      "InnerRunnable1: 2",
      "ThreadMethod: 2",
      "InnerThread1: 1",
      "InnerRunnable1: 1",
      "InnerRunnable2: 1",
      "ThreadMethod: 1"
    }, Test.IGNORE_ORDER + Test.DELAY_SHORT);
  }
} ///:~


InnerThread1 creates a named inner class which extends Thread, and makes an instance of this inner class inside the constructor. This makes sense if the inner class has special capabilities (new methods) that you need to access in other methods. However, most of the time the reason for creating a thread is only to use the Thread capabilities, and so it’s not necessary to created a named inner class. InnerThread2 shows the alternative: an anonymous inner subclass of Thread is created inside the constructor, and upcast to a Thread reference t. If other methods of the class need to access t, they can do so through the Thread interface and they don’t need to know the exact type of the object.

The third and fourth classes in the example repeat the first two classes, but using the Runnable interface rather than the Thread class. This is just to show that Runnable doesn’t buy you anything more in this situation but is in fact slightly more complicated to code (and to read the code). As a result, my inclination is to use Thread unless I’m somehow compelled to use Runnable.

The ThreadMethod class shows the creation of a thread inside a method. You call the method when you’re ready to run the thread, and the method returns after the thread begins. If the thread is only performing an auxiliary operation rather than being fundamental to the class, this is probably a more useful/appropriate approach than starting a thread inside the constructor of the class.

Creating responsive user interfaces

As stated earlier, one of the motivations for using threading is to create a responsive user interface. Although we haven’t gotten to graphical user interfaces yet in the book, you can see a simple example of a console-based user interface. The following example has two versions, one which gets stuck in a calculation and thus can never read console input, and a second which puts the calculation inside a thread and thus can be performing the calculation and listening for console input.

//: c14:ResponsiveUI.java
// User interface responsiveness.
// {RunByHand}
import com.bruceeckel.simpletest.*;

class UnresponsiveUI {
  private volatile double d = 1;
  public UnresponsiveUI() throws Exception {
    while(d > 0)
      d = d + (Math.PI + Math.E) / d;
    System.in.read(); // Never gets here
  }
}  

public class ResponsiveUI extends Thread {
  private volatile static double d = 1;
  public ResponsiveUI() {
    setDaemon(true);
    start();
  }
  public void run() {
    while(true) {
      d = d + (Math.PI + Math.E) / d;
    }
  }
  public static void main(String[] args) throws Exception {
    //! new UnresponsiveUI(); // Must kill this process
    new ResponsiveUI();
    System.in.read();
    System.out.println(d); // Shows progress
  }
} ///:~


UnresponsiveUI performs a calculation inside an infinite while loop, so it can obviously never reach the console input line (the compiler is fooled into believing the input line is reachable by the while conditional). If you run the program with the line that creates an UnresponsiveUI uncommented, you’ll have to kill the process to get out.

To make the program responsive, putting the calculation inside a run() method allows it to be preempted, and when you press the “Enter” key you’ll see that the calculation has indeed been running in the background while waiting for your user input.

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. Comment

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. Comment

Improperly accessing resources

Consider a variation on the counters that have been used so far in this chapter. In the following example, the class “guarantees” that it will always deliver an even number when you call getValue(). However, there’s a second thread named “Watcher” that is constantly calling getValue() and checking to see if this value is truly even. This seems like a needless activity, since looking at the code it appears obvious that the value will indeed be even. But that’s where the surprise comes in. Here’s the first version of the program: Comment

//: c14:AlwaysEven.java
// Demonstrating thread collision over resources by
// reading an object in an unstable intermediate state.

public class AlwaysEven {
  private int i;
  public void next() { i++; i++; }
  public int getValue() { return i; }
  public static void main(String args[]) {
    final AlwaysEven ae = new AlwaysEven();
    new Thread("Watcher") {
      public void run() {
        while(true) {
          int val = ae.getValue();
          if(val % 2 != 0) {
            System.out.println(val);
            System.exit(0);
          }
        }
      }
    }.start();
    while(true)
      ae.next();
  }
} ///:~


In main(), an AlwaysEven object is created – it must be final because it is accessed inside the anonymous inner class defined as a Thread. If the value read by the thread is not even, it prints it out (as proof that it has caught the object in an unstable state) and then exits the program. Comment

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

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. Comment

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

A resource testing framework

Before going on, let’s try to simplify things a bit by creating a little framework for performing tests on these types of threading examples. We can accomplish this by separating out the common code that might appear across multiple examples. First, note that the “watcher” thread is actually watching for a violated invariant in a particular object. That is, the object is supposed to preserve rules about its internal state, and if you can see the object, from outside, in an invalid intermediate state then the invariant has been violated, from the standpoint of the client (this is not to say that the object can never exist in the invalid intermediate state, just that it should not be visible by the client in such a state). Thus, we want to be able to detect that the invariant is violated, and also know what the violation value is. To get both of these values from one method call, we combine them in a “messenger” interface. This is a tagging interface which only exists to provide a meaningful name in the code:

//: c14:InvariantState.java
// Messenger carrying invariant data
public interface InvariantState {} ///:~


In this scheme, the information about success or failure is encoded in the class name and type, to make the result more readable. The class indicating success is:

//: c14:InvariantOK.java
// Indicates that the invariant test succeeded
public class InvariantOK implements InvariantState {} ///:~


For failure, the object will carry an object with information about what caused the failure, typically so that it can be displayed:

//: c14:InvariantFailure.java
// Indicates that the invariant test failed

public class InvariantFailure implements InvariantState {
  public Object value;
  public InvariantFailure(Object value) {
    this.value = value;
  }
} ///:~


Now we can define an interface that must be implemented by any class that wishes to have its invariance tested:

//: c14:Invariant.java
public interface Invariant {
  InvariantState invariant();
} ///:~


Before creating the generic “watcher” thread, note that some of the examples in this chapter will not behave as expected on all platforms. Many of the examples here attempt to show violations of single-threaded behavior when multiple threads are present, and this may not always happen[64]. Alternatively, an example may attempt to show that the violation does not occur by attempting (and failing) to demonstrate the violation. In these cases we’ll need a way to stop the program after a few seconds. The following class is a generic tool which does this:

//: c14:Timeout.java
// Set a time limit on the execution of a program

class Timeout extends Thread {
  private int delay;
  private String message;
  public Timeout(int delay, String msg) {
    this.delay = delay;
    message = msg;
    setDaemon(true);
    start(); 
  }
  public void run() {
    try {
      sleep(delay);
    } catch(InterruptedException e) {
      throw new RuntimeException(e);
    }
    System.out.println(message);
    System.exit(0);
  }
} ///:~


The delay is in milliseconds, and the message will be printed if the timeout expires. Note that this is created as a daemon thread so that if your program completes in some other way this thread will not prevent it from exiting.

Now we can use the Invariant interface and the Timeout class in the InvariantWatcher thread:

//: c14:InvariantWatcher.java
// Repeatedly checks to ensure invariant is not violated

public class InvariantWatcher extends Thread {
  Invariant invariant;
  public InvariantWatcher(Invariant invariant) {
    this.invariant = invariant;
    setDaemon(true);
    start(); 
  }
  // Stop everything after awhile:
  public 
  InvariantWatcher(Invariant invariant, final int timeOut){
    this(invariant);
    new Timeout(timeOut, 
      "Timed out without violating invariant");
  }
  public void run() {
    while(true) {
      InvariantState state = invariant.invariant();
      if(state instanceof InvariantFailure) {
        System.out.println("Invariant violated: " 
          + ((InvariantFailure)state).value);
        System.exit(0);
      }
    }
  }
} ///:~


The constructor captures a reference to the Invariant object to be tested, and starts the thread. The second constructor calls the first constructor, then creates a Timeout which stops everything after a desired delay – this is used in situations where the program may not exit by violating an invariant. In run(), the current InvariantState is captured and tested, and if it fails then the value is printed. Note that we cannot throw an exception inside this thread because that would only terminate the thread, not the program.

Now AlwaysEven.java can be rewritten using the above framework:

//: c14:EvenGenerator.java
// AlwaysEven.java using the invariance tester

public class EvenGenerator implements Invariant {
  private int i;
  public void next() { i++; i++; }
  public int getValue() { return i; }
  public InvariantState invariant() {
    int val = i; // Capture it in case it changes
    if(val % 2 == 0)
      return new InvariantOK();
    else
      return new InvariantFailure(new Integer(val));
  }
  public static void main(String args[]) {
    EvenGenerator gen = new EvenGenerator();
    new InvariantWatcher(gen);
    while(true)
      gen.next();
  }
} ///:~


When defining the invariant() method, you must capture all the values of interest into local variables. This way, you can return the actual value you have tested, not one that may have been changed (by another thread) in the meantime.

In this case, the problem is not that the object ever goes through a state that violates invariance, but that methods can be called by threads while the object is in that intermediate unstable state.

Colliding over resources

The worst thing that happens with EvenGenerator is that a client thread might see it in an unstable intermediate state. The object’s internal consistency is maintained, however, and it eventually becomes visible in a good state. But if two threads are actually modifying an object, the contention over shared resources is much worse, because the object can be put into an incorrect state.

Consider the simple concept of a semaphore, which is a flag object used for communication between threads. If the semaphore’s value is zero, then whatever it is monitoring is available, but if the value is nonzero then the monitored entity is unavailable, and the thread must wait for it. When it’s available, the thread increments the semaphore and then goes ahead and uses the monitored entity. Because incrementing and decrementing are atomic operations (that is, they cannot be interrupted), the semaphore keeps two threads from using the same entity at the same time.

If the semaphore is going to properly guard the entity that it is monitoring, then it must never get into an unstable state. Here’s a simple version of the semaphore idea:

//: c14:Semaphore.java
// A simple threading flag

public class Semaphore implements Invariant {
  private volatile int semaphore = 0;
  public boolean available() { return semaphore == 0; }
  public void acquire() { ++semaphore; }
  public void release() { --semaphore; }
  public InvariantState invariant() {
    int val = semaphore;
    if(val == 0 || val == 1)
      return new InvariantOK();
    else
      return new InvariantFailure(new Integer(val));
  }
} ///:~


The core part of the class is straightforward, consisting of available(), acquire(), and release(). Since a thread should check for availability before acquiring, the value of semaphore should never be other than one or zero, and this is tested by invariant().

But look what happens when Semaphore is tested for thread consistency:

//: c14:SemaphoreTester.java
// Colliding over shared resources

public class SemaphoreTester extends Thread {
  private volatile Semaphore semaphore;
  public SemaphoreTester(Semaphore semaphore) { 
    this.semaphore = semaphore;
    setDaemon(true);
    start(); 
  }
  public void run() {
    while(true)
      if(semaphore.available()) {
        yield(); // Makes it fail faster
        semaphore.acquire();
        yield();
        semaphore.release();
        yield();
      }
  }
  public static void main(String args[]) throws Exception {
    Semaphore sem = new Semaphore();
    new SemaphoreTester(sem);
    new SemaphoreTester(sem);
    new InvariantWatcher(sem).join();
  }
} ///:~


The SemaphoreTester creates a thread that continuously tests to see if a Semaphore object is available, and if so acquires and releases it. Note that the semaphore field is volatile to make sure that the compiler doesn’t optimize away any reads of that value.

In main(), two SemaphoreTester threads are created, and you’ll see that in short order the invariant is violated. This happens because one thread might get a true result from calling available(), but by the time that thread calls acquire(), the other thread may have already called acquire() and incremented the semaphore field. The InvariantWatcher may see the field with too high a value, or possibly see it after both threads have called release() and decremented it to a negative value. Note that InvariantWatcher join()s with the main thread to keep the program running until there is a failure.

On my machine, I discovered that the inclusion of yield() caused failure to occur much faster, but this will vary with operating systems and JVM implementations. You should experiment with taking the yield() statements out – the failure might take a very long time to occur, which demonstrates how difficult it can be to detect a flaw in your program when you’re writing multithreaded code.

This class emphasizes the risk of concurrent programming: if a class this simple can produce problems, you can never trust any assumptions about concurrency.

Resolving shared resource contention

To solve the problem of thread collision, virtually all multithreading schemes serialize access to shared resources. This means that only one thread at a time is allowed to access the shared resource. This is ordinarily accomplished by putting a locked clause around a piece of code, so that only one thread at a time may pass through that piece of code. Since this locked clause produces mutual exclusion, a common name for such a mechanism is mutex.

Consider the bathroom in your house – multiple people (threads) may each want to have exclusive use of the bathroom (the shared resource). To access the bathroom, a person knocks on the door to see if it’s available. If so, they enter and lock the door. Any other thread that wants to use the bathroom is “blocked” from using it, so that thread waits at the door until the bathroom is available.

The analogy breaks down a bit when the bathroom is released and it comes time to give access to another thread. There isn’t actually a line of people and we don’t know for sure who gets the bathroom next, because the thread scheduler isn’t deterministic that way. Instead, it’s as if there is a group of blocked threads milling about in front of the bathroom, and when the thread that has locked the bathroom unlocks it and emerges, the one that happens to be nearest the door at the moment goes in. As noted earlier, suggestions can be made to the thread scheduler via yield() and setPriority(), but these suggestions may not have much of an effect depending on your platform and JVM implementation.

Java has built-in support to prevent collisions over resources in the form of the synchronized keyword. This works much like the Semaphore class was supposed to: when a thread wishes to execute a piece of code guarded by the synchronized keyword, it checks to see if the semaphore is available, then acquires it, executes the code, and releases it. However, synchronized is built into the language so it’s guaranteed to always work, unlike the Semaphore class.

The shared resource is typically just a piece of memory in the form of an object, but may also be a file or IO port or something like a printer. To control access to a shared resource, you first put it inside an object. Then any method that accesses that resource can be made synchronized. This means that if a thread is inside one of the synchronized methods, all other threads are blocked from entering any of the synchronized methods of the class until the first thread returns from its call.

Since you typically make the data elements of a class private and access that memory only through methods, you can prevent collisions by making methods synchronized. Here is how you declare synchronized methods:

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


Each object contains a single lock 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 is a single lock that is shared by all the synchronized methods of a particular object, and this lock prevents common memory from being written by more than one thread at a time. Comment

One thread may acquire an object’s lock multiple times. This happens if one method calls a second method on the same object, which in turn calls another method on the same object, etc.. The JVM keeps track of the number of times the object has been locked. If the object is unlocked, it has a count of zero. As a thread acquires the lock for the first time, the count goes to one. Each time the thread acquires a lock on the same object, the count is incremented. Naturally, multiple lock acquisition is only allowed for the thread that acquired the lock in the first place. Each time the thread releases a lock, the count is decremented, until the count goes to zero, releasing the lock entirely for use by other threads. Comment

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. Comment

Synchronizing the EvenGenerator

By adding synchronized to EvenGenerator.java, we can prevent the undesirable thread access:

//: c14:SynchronizedEvenGenerator.java
// Using "synchronized" to prevent thread collisions

public 
class SynchronizedEvenGenerator implements Invariant {
  private int i;
  public synchronized void next() { i++; i++; }
  public synchronized int getValue() { return i; }
  // Not synchronized so it can run at any time and
  // thus be a genuine test:
  public InvariantState invariant() {
    int val = getValue();
    if(val % 2 == 0)
      return new InvariantOK();
    else
      return new InvariantFailure(new Integer(val));
  }
  public static void main(String args[]) {
    SynchronizedEvenGenerator gen =
      new SynchronizedEvenGenerator();
    new InvariantWatcher(gen, 4000); // 4-second timeout
    while(true)
      gen.next();
  }
} ///:~


You’ll notice that both next( ) and getValue( ) 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. On the other hand, InvariantState is not synchronized because it is doing the testing and we want it to be called at any time, so that it produces a true test of the object. Comment

Atomic operations

A common piece of lore often repeated in Java threading discussions is that “atomic operations do not need to be synchronized.” An atomic operation is one that cannot be interrupted by the thread scheduler – if the operation begins, then it will run to completion before the possibility of a context switch (switching execution to another thread).

The atomic operations commonly mentioned in this lore include simple assignment and returning a value when the variable in question is a primitive type that is not a long or a double. The latter types are excluded because they are larger than the rest of the types, and the JVM is thus not required to perform reads and assignments as single atomic operations (a JVM may choose to do so, anyway, but there’s no guarantee). However, you do get atomicity if you use the volatile keyword with long or double.

If you were to blindly apply the idea of atomicity to SynchronizedEvenGenerator.java, you would notice that

  public synchronized int getValue() { return i; }


fits the description. But try removing synchronized and the test will fail, because even though return i is indeed an atomic operation, removing synchronized allows the value to be read while the object is in an unstable intermediate state. You must genuinely understand what you’re doing before you try to apply optimizations like this. There are no easily-applicable rules that work.

As a second example, consider something even simpler: a class that produces serial numbers[65]. Each time nextSerialNumber() is called, it must return a unique value to the caller:

//: c14:SerialNumberGenerator.java

public class SerialNumberGenerator {
  private static volatile int serialNumber = 0;
  public static int nextSerialNumber() { 
    return serialNumber++;
  }
} ///:~


SerialNumberGenerator is about as simple a class as you can imagine, and if you’re coming from C++ or some other low-level background, you would expect the increment to be an atomic operation, because increment is usually implemented as a microprocessor instruction. However, in the JVM an increment is not atomic and involves both a read and a write, so there’s room for threading problems even in such a simple operation.

The serialNumber field is volatile because it is possible for each thread to have a local stack and maintain copies of some variables there. If you define a variable as volatile, it tells the compiler not to do any optimizations that would remove reads and writes that keep the field in exact synchronization with the local data in the threads.

To test this, we need a set which doesn’t run out of memory, in case it takes a long time to detect a problem. The CircularSet shown here reuses the memory used to store ints, with the assumption that by the time you wrap around, the possibility of a collision with the overwritten values is minimal. The add() and contains() methods are synchronized to prevent thread collisions:

//: c14:SerialNumberChecker.java
// Operations that may seem safe are not,
// when threads are present.

// Reuses storage so we don't run out of memory:
class CircularSet {
  int[] array;
  int len;
  int index = 0;
  public CircularSet(int size) {
    array = new int[size];
    len = size;
    // Initialize to a value not produced
    // by the SerialNumberGenerator:
    for(int i = 0; i < size; i++)
      array[i] = -1;
  }
  public synchronized void add(int i) {
    array[index] = i;
    // Wrap index and write over old elements:
    index = ++index % len;
  }
  public synchronized boolean contains(int val) {
    for(int i = 0; i < len; i++)
      if(array[i] == val) return true;
    return false;
  }
}

public class SerialNumberChecker {
  private static CircularSet serials = 
    new CircularSet(1000);
  static class SerialChecker extends Thread {
    SerialChecker() { start(); }
    public void run() {
      while(true) {
        int serial = 
          SerialNumberGenerator.nextSerialNumber();
        if (serials.contains(serial)) {
          System.out.println("Duplicate: " + serial);
          System.exit(0);
        }
        serials.add(serial);
      }
    }
  }
  public static void main(String args[]) {
    for(int i = 0; i < 10; i++)
      new SerialChecker();
    // Stop after 4 seconds:
    new Timeout(4000, "No duplicates detected");
  }
} ///:~


SerialNumberChecker contains a static CircularSet which contains all the serial numbers that have been extracted, and a nested Thread that gets serial numbers and ensures that they are unique. By creating two threads to contend over serial numbers, you’ll discover that both threads get a duplicate serial number reasonably soon (note that this program may not indicate a collision on your machine, but it has successfully detected collisions on a multiprocessor machine). To solve the problem, add the synchronized keyword to nextSerialNumber().

The atomic operations that are supposed to be safe are reading and assignment of primitives. However, as seen in EvenGenerator.java, it’s still easily possible to use an atomic operation that accesses your object while it’s in an unstable intermediate state, and so you cannot make any assumptions. On top of this, the atomic operations are not guaranteed to work with long and double (although some JVM implementations do guarantee atomicity for long and double operations, you won’t be writing portable code if you depend on this).

It’s safest to use the following guidelines:

  1. If you need to synchronize one method in a class, synchronize all of them. It’s often difficult to tell for sure if a method will be negatively affected if you leave synchronization out.
  2. Be extremely careful when removing synchronization from methods. The typical reason to do this is for performance, and in JDK 1.3 and 1.4 the overhead of synchronized has been greatly reduced. In addition, you should only do this after using a profiler to determine that synchronized is the bottleneck.

Fixing Semaphore

Now consider Semaphore.java. It would seem that we should be able to repair this by synchronizing the three class methods, like this:

//: c14:SynchronizedSemaphore.java
// Colliding over shared resources

public class SynchronizedSemaphore extends Semaphore {
  private volatile int semaphore = 0;
  public synchronized boolean available() { 
    return semaphore == 0; 
  }
  public synchronized void acquire() { ++semaphore; }
  public synchronized void release() { --semaphore; }
  public InvariantState invariant() {
    int val = semaphore;
    if(val == 0 || val == 1)
      return new InvariantOK();
    else
      return new InvariantFailure(new Integer(val));
  }
  public static void main(String args[]) throws Exception {
    SynchronizedSemaphore sem =new SynchronizedSemaphore();
    new SemaphoreTester(sem);
    new SemaphoreTester(sem);
    new InvariantWatcher(sem).join();
  }
} ///:~


This looks rather odd at first – SynchronizedSemaphore is inherited from Semaphore, and yet all the overridden methods are synchronized, while the base-class versions aren’t. Java doesn’t allow you to change the method signature during overriding, and yet doesn’t complain about this. That’s because the synchronized keyword is not part of the method signature, so you can add it in and it doesn’t limit overriding.

The reason for inheriting from Semaphore is to reuse the SemaphoreTester class. When you run the program you’ll see that it still causes an InvariantFailure.

Why does this fail? By the time a thread detects that the Semaphore is available because available() returns true, it has released the lock on the object. Another thread can dash in and increment the semaphore value before the first thread does. The first thread still assumes the Semaphore object is available and so goes ahead and blindly enters the acquire() method, putting the object into an unstable state. This is just one more lesson about rule zero of concurrent programming: never make any assumptions.

The only solution to this problem is to make the test for availability and the acquisition a single atomic operation – which is exactly what the synchronized keyword provides in conjunction with the lock on an object. That is, Java’s lock and synchronized keyword is a built-in semaphore mechanism, so you don’t need to create your own.

[[ Current rewrite stops here... more later ]]

Critical sections

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: Comment

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


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

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

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


This is the only change that must be made to Sharing2.java, and 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( ). Comment

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

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. Comment


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. Comment
  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. Comment
  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. Comment
  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. Comment

Becoming blocked

When a thread is blocked, there’s some reason that it cannot continue running. A thread can become blocked for the following reasons: Comment

  1. You’ve put the thread to sleep by calling sleep(milliseconds), in which case it will not be run for the specified time. Comment
  2. 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.) Comment
  3. The thread is waiting for some I/O to complete. Comment
  4. The thread is trying to call a synchronized method on another object, and that object’s lock is not available. Comment

In old code, you may also see suspend( ) and resume( ) used to block and unblock threads, but these are deprecated in Java 2 (because they are deadlock-prone), and will not be examined further. Comment

Wait and notify

It’s important to understand that both sleep( ) does not release the lock when it is 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. Comment

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

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

One fairly unique aspect of wait( ) and notify( ) is that both methods are part of the base class Object and not part of Thread as is sleep( ). 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( ) can be called within non-synchronized methods since it doesn’t manipulate the lock. Comment

You can call wait( ) or notify( ) only for your own lock. Again, you can compile code that tries to use the wrong lock, but it will produce the same IllegalMonitorStateException message as before. You 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. Comment

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. Comment

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). Comment

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

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");
    }
  }
} 


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

To set up a connection between the Sender and Receiver objects, a PipedWriter and PipedReader are created. Note that the PipedReader in must be connected to the PipedWriter out via a constructor argument. After that, anything 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). Comment

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. Comment

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. Comment

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. Comment

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. Comment

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. Comment

The suspend( ) and resume( ) methods turn out to be inherently deadlock-prone. When you call suspend( ), the target thread stops but it still holds any locks that it has acquired up to that point. So no other thread can access the locked resources until the thread is resumed. Any thread that wants to resume the target thread and also tries to use any of the locked resources produces deadlock. You should not use suspend( ) and resume( ), but instead put a flag in your Thread class to indicate whether the thread should be active or suspended. If the flag indicates that the thread is suspended, the thread goes into a wait using wait( ). When the flag indicates that the thread should be resumed the thread is restarted with notify( ). An example can be produced by modifying Counter2.java. Although the effect is similar, 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: Comment

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

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


The flag suspended inside Suspendable is used to turn suspension on and off. To suspend, the flag is set to true by calling fauxSuspend( ) and this is detected inside run( ). The wait( ), as described earlier in this chapter, must be synchronized so that it has the object lock. In fauxResume( ), the suspended flag is set to false and notify( ) is 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( ). Comment

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. Comment

You might wonder why these methods, now deprecated, were included in Java in the first place. It seems an admission of a rather significant mistake to simply remove them outright (and pokes yet another hole in the arguments for 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. Comment

Thread groups

The value of thread groups can be summed up by a quote from Joshua Bloch[66], the software architect at Sun who fixed the Java collections library in JDK 1.2: Comment

“Thread groups are best viewed as an unsuccessful experiment, and you may simply ignore their existence.”

If you’ve spent time and energy trying to figure out the value of thread groups (as I have), you may wonder why there was not some more official announcement from Sun on the topic, sooner than this (the same question could be asked about any number of other changes that have happened to Java over the years). The Nobel Laureate economist Joseph Stiglitz has a philosophy of life which would seem to apply here (and in a number of other places throughout the experience of Java. Well, why stop there? – I’ve consulted on more than a few projects where this has applied). It’s called The Theory of Escalating Commitment: Comment

"The cost of continuing mistakes is borne by others, while the cost of admitting mistakes is borne by yourself."

There is one tiny remaining use for thread groups. If a thread in the group throws an uncaught exception, ThreadGroup.uncaughtException() is invoked, which prints a stack trace to the standard error stream. If you want to modify this behavior, you must override this method. Comment

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. Comment

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
  5. Inconsistencies across platforms. For instance, while developing some of the examples for this book I discovered race conditions that quickly appeared on some computers which wouldn’t appear on others. If you developed a program on the latter, you might get badly surprised when you distribute it.

An additional advantage to threads is that they provide “light” execution context switches (on the order of 100 instructions) rather than “heavy” process context switches (thousands 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. A process change –the heavy context switch – must exchange the full memory space. Comment

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

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. Comment

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 can be hard to detect, and will often depend on the OS and JVM; it could be less than a hundred or in the thousands. As you often create only a handful of threads to solve a problem, this is typically not much of a limit, yet in a more general design it becomes a constraint. Comment

A significant nonintuitive issue in threading is that, because of thread scheduling, you can typically make your applications run faster by inserting calls to yield() or even 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. The reason this happens is that shorter delays can cause the end-of-sleep( ) scheduler interrupt to happen before the running thread is ready to go to sleep, forcing the scheduler to stop it and restart it later so it can finish what it was doing and then go to sleep. It takes extra thought to realize how messy things can get. Comment

For more advanced discussions of threading, see Concurrent Programming in Java, 2nd Edition, by Doug Lea, Addison-Wesley, 2000. Comment

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. Comment
  2. Modify Sharing2.java to add a synchronized block inside the run( ) method of TwoCounter instead of synchronizing the entire run( ) method. Comment
  3. Create two Thread subclasses, one with a run( ) that starts up and then calls wait( ). The other class run( ) should capture the reference of the first Thread object. Its run( ) should call notifyAll( ) for the first thread after some number of seconds have passed, so the first thread can print a message. Comment
  4. In Counter5.java inside Ticker2, remove the yield( ) and explain the results. Replace the yield( ) with a sleep( ) and explain the results. Comment
  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. Comment
  6. Change Daemons.java so that main( ) has a sleep( ) instead of a readLine( ). Experiment with different sleep times to see what happens. Comment
  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. Comment
  8. Modify Exercise 7 so that the java.util.Timer class found in JDK 1.3 is used to run the system. Comment
  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 scroll 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. Comment
  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. Comment
  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. Comment
  12. Modify SimpleThread.java so that all the threads are daemon threads, and verify that the program ends as soon as main( ) is able to exit. Comment



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

[64] Some examples were developed on a dual-processor Win2K machine which would immediately show collisions. However, the same example run on single-processor machines might run for extended periods without demonstrating a collision – this is the kind of scary behavior that makes multithreading difficult. You can imagine developing on a single-processor machine and thinking that your code is thread safe, then discovering breakages as soon as it’s moved to a multiprocessor machine.

[65] Inspired by Joshua Bloch’s Effective Java, Addison-Wesley 2001, page 190.

[66] Effective Java, by Joshua Bloch, Addison-Wesley 2001, page 211.


Previous Next Title Page Index Contents