Untangling Java concurrency

Java 101: Understanding Java threads, Part 4: Thread groups, volatility, and thread-local variables

Final concepts for improving Java application programming with Java threads

1 2 3 4 Page 3
Page 3 of 4

ThreadLocalDemo2 is nearly identical to ThreadLocalDemo1. However, instead of overriding initialValue() to establish each thread's initial value to a unique serial number, ThreadLocalDemo2 uses a tl.set ("" + sernum++); method call. If you run this program, your output will be more or less identical (on an invocation-by-invocation basis) to ThreadLocalDemo1's output.

Before leaving this section, we must consider one other topic: inheritance and thread-local variables. When a thread creates another thread, the creating thread is the parent thread and the created thread is the child thread. For example, the main thread that executes the main() method's byte-code instructions is the parent of all threads that those instructions create. A child cannot inherit a parent's thread-local values that the parent thread establishes via the ThreadLocal class. However, a parent can use java.lang.InheritableThreadLocal (which extends ThreadLocal) to pass the values of inheritable thread-local variables to a child, as Listing 7 demonstrates:

Listing 7. InheritableThreadLocalDemo.java

// InheritableThreadLocalDemo.java
class InheritableThreadLocalDemo implements Runnable
{
   static InheritableThreadLocal itl = new InheritableThreadLocal ();
   static ThreadLocal tl = new ThreadLocal ();
   public static void main (String [] args)
   {
      itl.set ("parent thread thread-local value passed to child thread");
      tl.set ("parent thread thread-local value not passed to child thread");
      InheritableThreadLocalDemo itld;
      itld = new InheritableThreadLocalDemo ();
      Thread child1 = new Thread (itld);
      Thread child2 = new Thread (itld);
      child1.start ();
      child2.start ();
   }
   public void run ()
   {
      System.out.println (itl.get ());
      System.out.println (tl.get ());
   }
}

InheritableThreadLocalDemo creates an inheritable thread-local variable via the InheritableThreadLocal class and a thread-local variable via the ThreadLocal class. Furthermore, the main thread calls each variable's set(Object value) method to establish an initial value before creating and starting two child threads. When each child calls get() to retrieve that value, only the inheritable thread-local variable's value returns, as the following output (from one invocation) demonstrates:

parent thread thread-local value passed to child thread
null
parent thread thread-local value passed to child thread
null

The output shows each child thread printing parent thread thread-local value passed to child thread, which is the inheritable thread-local variable's value. However, null prints as the ThreadLocal variable's value.

Tip: Override InheritableThreadLocal's childValue(Object parentvalue) method to make the child's inheritable thread-local value a function of the parent's inheritable thread-local value.

Timers

Programs occasionally need a timer mechanism to execute code either once or periodically, and either at some specified time or after a time interval. Before Sun released J2SE 1.3, a developer either created a custom timer mechanism or relied on another's mechanism. Incompatible timer mechanisms led to difficult-to-maintain source code. Recognizing a need to standardize timer mechanisms, Sun introduced two timer classes in SDK 1.3: java.util.Timer and java.util.TimerTask.

Note: Sun also introduced the javax.swing.Timer class in the 1.3 SDK. I don't present that class here because that discussion requires Swing knowledge. After I introduce you to Swing in a future article, I'll explore Swing's Timer class.

According to the SDK, use a Timer object to schedule tasksTimerTask and subclass objects—for execution. That execution relies on a thread associated with the Timer object. To create a Timer object, call either the Timer() or Timer(boolean isDaemon) constructor. The constructors differ in the threads they create to execute tasks: Timer() creates a nondaemon thread, whereas Timer(boolean isDaemon) creates a daemon thread, when isDaemon contains true. The following code demonstrates both constructors creating Timer objects:

Timer t1 = new Timer (); // Create a nondaemon thread to execute all tasks
Timer T2 = new Timer (true); // Create a daemon thread to execute all tasks

Once you create a Timer object, you need a TimerTask to execute. Do that by subclassing TimerTask and overriding TimerTask's run() method. (TimerTask implements Runnable, which specifies the run() method.) The following code fragment demonstrates:

class MyTask extends TimerTask
{
   public void run ()
   {
      System.out.println ("MyTask task is running.");
   }
}

Now that you have a Timer object and a TimerTask subclass, to schedule a TimerTask object for one-time or repeated execution, call one of Timer's four schedule() methods:

  1. void schedule(TimerTask task, Date time): Schedules task for one-time execution at the specified time.
  2. void schedule(TimerTask task, Date firstTime, long interval): Schedules task for repeated execution at the specified firstTime and at interval millisecond intervals following firstTime. This execution is known as fixed-delay execution because each subsequent task execution occurs relative to the previous task execution's actual execution time. Furthermore, if an execution delays because of garbage collection or some other background activity, all subsequent executions also delay.
  3. void schedule(TimerTask task, long delay): Schedules task for one-time execution after delay milliseconds pass.
  4. void schedule(TimerTask task, long delay, long interval): Schedules task for repeated execution after delay milliseconds pass and at interval millisecond intervals following firstTime. This method employs fixed-delay execution.

The following code fragment, which assumes a t1-referenced Timer object, creates a MyTask object and schedules that object using the fourth method in the above list for repeated execution (every second), following an initial delay of zero milliseconds:

t1.schedule (new MyTask (), 0, 1000);

Every second (that is, 1,000 milliseconds), the Timer's thread executes the MyTask's run() method. For a more useful example of task execution, check out Listing 8:

Listing 8. Clock1.java

// Clock1.java
// Type Ctrl+C (or equivalent keystroke combination on non-Windows platform) 
// to terminate
import java.util.*;
class Clock1
{
   public static void main (String [] args)
   {
      Timer t = new Timer ();
      t.schedule (new TimerTask ()
                  {
                      public void run ()
                      {
                         System.out.println (new Date ().toString ());
                      }
                  },
                  0,
                  1000);
   }
}

Clock1 creates a Timer object and calls schedule(TimerTask task, long delay, long interval) to schedule fixed-delay executions of an anonymous TimerTask subclass object's run() method. That method retrieves the current date by calling java.util.Date's Date() constructor and converts the Date object's contents to a human-readable String, which subsequently prints. The following partial output shows the results of running this program:

Mon Jul 01 16:23:49 CDT 2002
Mon Jul 01 16:23:51 CDT 2002
Mon Jul 01 16:23:52 CDT 2002
Mon Jul 01 16:23:53 CDT 2002
Mon Jul 01 16:23:54 CDT 2002
Mon Jul 01 16:23:55 CDT 2002
Mon Jul 01 16:23:56 CDT 2002
Mon Jul 01 16:23:57 CDT 2002

In addition to the four schedule() methods, Timer includes two scheduleAtFixedRate() methods:

  1. void scheduleAtFixedRate(TimerTask task, Date firstTime, long interval): Schedules task for repeated execution at the specified firstTime and at interval millisecond intervals following firstTime. This execution is known as fixed-rate execution because each subsequent task execution occurs relative to the initial task execution. Furthermore, if an execution delays because of garbage collection or some other background activity, two or more executions occur in rapid succession to maintain the execution frequency.
  2. void scheduleAtFixedRate(TimerTask task, long delay, long interval): Schedules task for repeated execution after delay milliseconds pass and at interval millisecond intervals following firstTime. This method employs fixed-rate execution.

Two of the four schedule() methods use fixed-delay execution, whereas both scheduleAtFixedRate() methods use fixed-rate execution. How do these execution styles differ? Fixed-delay execution promotes an accurate frequency in the short run versus the long run. This execution style is appropriate for tasks that must operate smoothly, such as many animation tasks and a blinking cursor, where erratic movements interrupt the programs' fluidity. In contrast to fixed-delay execution, fixed-rate execution promotes total frequency accuracy at the expense of execution smoothness. This style is appropriate for counters and clocks that should not miss a single execution. Why is that important? In Clock1's output, you see the time moving from Mon Jul 01 16:23:49 CDT 2002 to Mon Jul 01 16:23:51 CDT 2002, with no intermediate Mon Jul 01 16:23:50 CDT 2002. Clock1's fixed-delay execution results in a loss of total frequency accuracy. We can correct that problem by using fixed-rate execution:

Listing 9. Clock2.java

// Clock2.java
// Type Ctrl+C (or equivalent keystroke combination on non-Windows platform)
// to terminate
import java.util.*;
class Clock2
{
   public static void main (String [] args)
   {
      Timer t = new Timer ();
      t.scheduleAtFixedRate (new TimerTask ()
                             {
                                public void run ()
                                {
                                   System.out.println (new Date ().
                                                       toString ());
                                }
                             },
                             0,
                             1000);
   }
}

With Clock2, you are less likely to miss seeing a single second because scheduleAtFixedRate(TimerTask task, long delay, long interval) promotes extra task executions when a delay occurs. But if the delay is excessive, the output can still omit various times.

Clock1 and Clock2 have a problem: Neither program provides platform-independent termination. Depending on the platform used, a user must type some keystroke combination to exit either program. To solve this problem, you could construct a daemon task execution thread by calling Timer (true). That way, the program ends when the main thread ends. However, because this technique does not allow the currently executing task to finish, it is problematic. Imagine what would happen if the task is writing to a file when the application ends. A better approach allows the main thread to initiate a thread that checks for user input and waits for the input thread to terminate once input occurs. The main thread then calls Timer's void cancel() method to terminate the timer and discard all scheduled tasks after the currently executing task leaves its run() method. Listing 10 demonstrates this approach:

Listing 10. Clock3.java

// Clock3.java
// Type Ctrl+C (or equivalent keystroke combination on non-Windows platform)
// to terminate
import java.util.*;
class Clock3
{
   public static void main (String [] args)
   {
      Timer t = new Timer ();
      t.scheduleAtFixedRate (new TimerTask ()
                             {
                                public void run ()
                                {
                                   System.out.println (new Date ().
                                                       toString ());
                                }
                             },
                             0,
                             1000);
      InputThread it = new InputThread ();
      it.start ();
      try
      {
          // Wait for input thread to terminate
          it.join ();
      }
      catch (InterruptedException e)
      {
      }
      // Terminate the timer and discard all scheduled tasks after the
      // currently executing task leaves its run() method
      t.cancel ();
   }
}
class InputThread extends Thread
{
   public void run ()
   {
      try
      {
         // Wait for user to type Enter key
         System.in.read ();
      }
      catch (java.io.IOException e)
      {
      }
   }
}

Tip: To terminate the currently running task without affecting other tasks, call TimerTask's boolean cancel() method. That method cancels the current task so that it will never run again (assuming the task is repeating) after it finishes its current execution and returns a Boolean true value if either the task is a one-time task that has not yet run or a repeating task. False returns if a one-time task has already run, if it was never scheduled, or if it was cancelled. TimerTask's cancel() method does not cancel any other tasks.

Thread death

Prior to the deprecation of Thread's stop() method, developers often called that method to terminate a thread. stop() throws a ThreadDeath object, causing the thread to exit from any point within run()'s execution by unwinding the thread's method-call stack. The JVM catches the thrown ThreadDeath object, yet does not display a stack trace.

Though you shouldn't use stop() anymore, you might need to stop a thread during its execution. Although thread termination normally involves returning from the run() method, that might prove difficult to accomplish if the thread's execution is deep within a nested set of method calls. By throwing a ThreadDeath object, a thread can unwind the method-call stack and terminate gracefully as Listing 11 shows:

Listing 11. ThreadDeathDemo.java

1 2 3 4 Page 3
Page 3 of 4