Untangling Java concurrency

Java 101: Understanding Java threads, Part 3: Thread scheduling and wait/notify

Learn about the mechanisms that help you set and manage thread priority

1 2 3 4 Page 4
Page 4 of 4
// WaitNotifyAllDemo.java
class WaitNotifyAllDemo
{
   public static void main (String [] args)
   {
      Object lock = new Object ();
      MyThread mt1 = new MyThread (lock);
      mt1.setName ("A");
      MyThread mt2 = new MyThread (lock);
      mt2.setName ("B");
      MyThread mt3 = new MyThread (lock);
      mt3.setName ("C");
      mt1.start ();
      mt2.start ();
      mt3.start ();
      System.out.println ("main thread sleeping");
      try
      {
         Thread.sleep (3000);
      }
      catch (InterruptedException e)
      {
      }
      System.out.println ("main thread awake");
      synchronized (lock)
      {
         lock.notifyAll ();
      }
   }
}
class MyThread extends Thread
{
   private Object o;
   MyThread (Object o)
   {
      this.o = o;
   }
   public void run ()
   {
      synchronized (o)
      {
         try
         {
             System.out.println (getName () + " before wait");
             o.wait ();
             System.out.println (getName () + " after wait");
         }
         catch (InterruptedException e)
         {
         }
      }
   }
}

WaitNotifyAllDemo's main thread creates three MyThread objects and assigns names A, B, and C to the associated threads, which subsequently start. The main thread then sleeps for three seconds to give the newly created threads time to wait. After waking up, the main thread calls notifyAll() to awaken those threads. One by one, each thread leaves its synchronized statement and run() method, then terminates.

Tip: This article demonstrates notify() in the ProdCons2 program because only one thread waits for a condition to occur. In your programs, where more than one thread might simultaneously wait for the same condition to occur, consider using notifyAll(). That way, no waiting thread waits indefinitely.

Thread interruption

One thread can interrupt another thread that is either waiting or sleeping by calling Thread's void interrupt(); method. In response, the waiting/sleeping thread resumes execution by creating an object from InterruptedException and throwing that object from the wait() or sleep() methods.

Note: Because the join() methods call sleep() (directly or indirectly), the join() methods can also throw InterruptedException objects.

When a call is made to interrupt(), that method either allows a waiting/sleeping thread to resume execution via a thrown exception object or sets a Boolean flag to true (somewhere in the appropriate thread object) to indicate that an executing thread has been interrupted. The method sets the flag only if the thread is neither waiting nor sleeping. An executing thread can determine the Boolean flag's state by calling one of the following Thread methods: static boolean interrupted(); (for the current thread) or boolean isInterrupted(); (for a specific thread). These methods feature two differences:

  1. Because interrupted() is a static method, you do not need a thread object before you call it. For example: System.out.println (Thread.interrupted ()); // Display Boolean flag value for current thread. In contrast, because isInterrupted() is a nonstatic method, you need a thread object before you call that method.
  2. The interrupted() method clears the Boolean flag to false, whereas the isInterrupted() method does not modify the Boolean flag.

In a nutshell, interrupt() sets the Boolean flag in a thread object and interrupted()/isInterrupted() returns that flag's state. How do we use this capability? Examine Listing 7's ThreadInterruptionDemo source code:

Listing 7. ThreadInterruptionDemo.java

// ThreadInterruptionDemo.java
class ThreadInterruptionDemo
{
   public static void main (String [] args)
   {
      ThreadB thdb = new ThreadB ();
      thdb.setName ("B");
      ThreadA thda = new ThreadA (thdb);
      thda.setName ("A");
      thdb.start ();
      thda.start ();
   }
}
class ThreadA extends Thread
{
   private Thread thdOther;
   ThreadA (Thread thdOther)
   {
      this.thdOther = thdOther;
   }
   public void run ()
   {
      int sleepTime = (int) (Math.random () * 10000);
      System.out.println (getName () + " sleeping for " + sleepTime +
                          " milliseconds.");
      try
      {
         Thread.sleep (sleepTime);
      }
      catch (InterruptedException e)
      {
      }
      System.out.println (getName () + " waking up, interrupting other " +
                          "thread and terminating.");
      thdOther.interrupt ();
   }
}
class ThreadB extends Thread
{
   int count = 0;
   public void run ()
   {
      while (!isInterrupted ())
      {
         try
         {
            Thread.sleep ((int) (Math.random () * 10));
         }
         catch (InterruptedException e)
         {
            System.out.println (getName () + " about to terminate...");
            // Because the Boolean flag in the consumer thread's thread
            // object is clear, we call interrupt() to set that flag.
            // As a result, the next consumer thread call to isInterrupted()
            // retrieves a true value, which causes the while loop statement
            // to terminate.
            interrupt ();
         }
         System.out.println (getName () + " " + count++);
      }
   }
}

ThreadInterruptionDemo starts a pair of threads: A and B. A sleeps for a random amount of time (up to 10 seconds) before calling interrupt() on B's thread object. B continually checks for interruption by calling its thread object's isInterrupted() method. As long as that method returns false, B executes the statements within the while loop statement. Those statements cause B to sleep for a random amount of time (up to 10 milliseconds), print variable count's value, and increment that value.

When A calls interrupt(), B is either sleeping or not sleeping. If B is sleeping, B wakes up and throws an InterruptedException object from the sleep() method. The catch clause then executes, and B calls interrupt() on its thread object to set B's Boolean flag to true. (That flag clears to false when the exception object is thrown.) The next call to isInterrupted() causes execution to leave the while loop statement because isInterrupted() returns true. The result: B terminates. If B is not sleeping, consider two scenarios. First, B has just called isInterrupted() and is about to call sleep() when A calls interrupt(). B's call to sleep() results in that method immediately throwing an InterruptedException object. This scenario is then identical to when B was sleeping: B eventually terminates. Second, B is executing System.out.println (getName () + " " + count++); when A calls interrupt(). B completes that method call and calls isInterrupted(). That method returns true, B breaks out of the while loop statement, and B terminates.

Review

This article continued to explore Java's threading capabilities by focusing on thread scheduling, the wait/notify mechanism, and thread interruption. You learned that thread scheduling involves either the JVM or the underlying platform's operating system deciphering how to share the processor resource among threads. Furthermore, you learned that the wait/notify mechanism makes it possible for threads to coordinate their executions—to achieve ordered execution, as in the producer-consumer relationship. Finally, you learned that thread interruption allows one thread to prematurely awaken a sleeping or waiting thread.

This article's material proves important for three reasons. First, thread scheduling helps you write platform-independent programs where thread scheduling is an issue. Second, situations (such as the producer-consumer relationship) arise where you must order thread execution. The wait/notify mechanism helps you accomplish that task. Third, you can interrupt threads when your program must terminate even though other threads are waiting or sleeping.

In next month's article, I'll conclude this series by exploring thread groups, volatility, thread local variables, and timers.

Jeff Friesen has been involved with computers for the past 20 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he has written his own Java book for beginners— Java 2 by Example, Second Edition (Que Publishing, 2001; ISBN: 0789725932)—and helped write Using Java 2 Platform, Special Edition (Que Publishing, 2001; ISBN: 0789724685). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he's working on, check out his Website at http://www.javajeff.com.

Learn more about this topic

  • Learn more about Java: See the complete listing for Jeff Friesen's Java 101 series -- archived on JavaWorld.
  • Also see the Java Tips series: More than five years of compiled tips from JavaWorld's expert readers.

1 2 3 4 Page 4
Page 4 of 4