CHAPTER 4

image

Additional Thread Capabilities

Chapters 1 through 3 introduced you to the java.lang.Thread class and java.lang.Runnable interface, to synchronization, and to waiting and notification. In this chapter, I complete my coverage of thread basics by introducing you to thread groups and thread-local variables. Also, I present the Timer Framework, which leverages Thread behind the scenes to simplify timer-oriented tasks.

Thread Groups

While exploring the Thread class, you’ve probably encountered references to the java.lang.ThreadGroup class in constructors such as Thread(ThreadGroup group, Runnable target), and in methods such as static int activeCount() and static int enumerate(Thread[] tarray).

The JDK documentation for ThreadGroup states that a thread group “represents a set of threads. In addition, a thread group can also include other thread groups. The thread groups form a tree in which every thread group except the initial thread group has a parent.”

Using a ThreadGroup object, you can perform an operation on all contained Thread objects. For example, assuming a thread group referenced by variable tg, tg.suspend(); suspends all of the threads in the thread group. Thread groups simplify the management of many threads.

Although ThreadGroup appears to be a very useful, you should largely avoid this class for the following reasons:

  • The most useful ThreadGroup methods are void suspend(), void resume(), and void stop(). These methods have been deprecated because, like their Thread counterparts (to which these methods delegate for each thread in the thread group), they are prone to deadlock and other problems.
  • ThreadGroup isn’t thread-safe. For example, to obtain a count of the active threads in a thread group, you would call ThreadGroup’s int activeCount() method. You would then use this value to size the array that you pass to one of ThreadGroup’s enumerate() methods. However, there is no guarantee that the count will remain accurate because, between the time you’ve created the array and the time you pass it to enumerate(), this count could change because of thread creation and termination. If the array is too small, enumerate() silently ignores extra threads. The same can be said of Thread’s activeCount() and enumerate() methods, which delegate to the ThreadGroup methods for the current thread. This problem is an example of the “time of check to time of use” (https://en.wikipedia.org/wiki/Time_of_check_to_time_of_use) class of software bug. (This bug also rears its ugly head in scenarios where you need to check for file existence before performing an operation on the file. Between the file check and the operation, the file might be deleted or created.)

However, you should still know about ThreadGroup because of its contribution in handling exceptions that are thrown while a thread is executing. Listing 4-1 sets the stage for learning about exception handling by presenting a run() method that attempts to divide an integer by 0, which results in a thrown java.lang.ArithmeticException object.

The default main thread creates a runnable that deliberately throws an ArithmeticException object by attempting to divide an integer by integer 0.

Compile Listing 4-1 as follows:

javac ExceptionThread.java

Run the resulting application as follows:

java ExceptionThread

You’ll see an exception trace that identifies the thrown instance of the ArithmeticException class:

Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
        at ExceptionThread$1.run(ExceptionThread.java:10)
        at java.lang.Thread.run(Thread.java:745)

When an exception is thrown out of the run() method, the thread terminates and the following activities take place:

  • The Java virtual machine (JVM) looks for an instance of Thread.UncaughtExceptionHandler installed via Thread’s void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) method. When this handler is found, it passes execution to the instance’s void uncaughtException(Thread t, Throwable e) method, where t identifies the Thread object of the thread that threw the exception, and e identifies the thrown exception or error—perhaps a java.lang.OutOfMemoryError object was thrown. If uncaughtException() throws an exception/error, the exception/error is ignored by the JVM.
  • Assuming that setUncaughtExceptionHandler() was not called to install a handler, the JVM passes control to the associated ThreadGroup object’s uncaughtException(Thread t, Throwable e) method. Assuming that ThreadGroup was not extended and that its uncaughtException() method was not overridden to handle the exception, uncaughtException() passes control to the parent ThreadGroup object’s uncaughtException() method when a parent ThreadGroup is present. Otherwise, it checks to see if a default uncaught exception handler has been installed (via Thread’s static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) method). If a default uncaught exception handler has been installed, its uncaughtException() method is called with the same two arguments. Otherwise, uncaughtException() checks its Throwable argument to determine if it’s an instance of java.lang.ThreadDeath. If so, nothing special is done. Otherwise, as Listing 4-1’s exception message shows, a message containing the thread’s name, as returned from the thread’s getName() method, and a stack backtrace, using the Throwable argument’s printStackTrace() method, is printed to the standard error stream.

Listing 4-2 demonstrates Thread’s setUncaughtExceptionHandler() and setDefaultUncaughtExceptionHandler() methods.

Compile Listing 4-2 (javac ExceptionThread.java) and run the resulting application (java ExceptionThread). You should observe this output:

Caught throwable java.lang.ArithmeticException: / by zero for thread Thread[Thread-0,5,main]

You will not also see the default uncaught exception handler’s output because the default handler isn’t called. To see that output, you must comment out thd.setUncaughtExceptionHandler(uceh);. If you also comment out thd.setDefaultUncaughtExceptionHandler(uceh);, you will see Listing 4-1’s output.

Thread-Local Variables

You will sometimes want to associate per-thread data (such a user ID) with a thread. Although you can accomplish this task with a local variable, you can only do so while the local variable exists. You could use an instance field to keep this data around longer, but then you would have to deal with synchronization. Thankfully, Java supplies the java.lang.ThreadLocal class as a simple (and very handy) alternative.

Each ThreadLocal instance describes a thread-local variable, which is a variable that provides a separate storage slot to each thread that accesses the variable. You can think of a thread-local variable as a multislot variable in which each thread can store a different value in the same variable. Each thread sees only its value and is unaware of other threads having their own values in this variable.

ThreadLocal is generically declared as ThreadLocal<T>, where T identifies the type of value that is stored in the variable. This class declares the following constructor and methods:

  • ThreadLocal(): Create a new thread-local variable.
  • T get(): Return the value in the calling thread’s storage slot. If an entry doesn’t exist when the thread calls this method, get() calls initialValue().
  • T initialValue(): Create the calling thread’s storage slot and store an initial (default) value in this slot. The initial value defaults to null. You must subclass ThreadLocal and override this protected method to provide a more suitable initial value.
  • void remove(): Remove the calling thread’s storage slot. If this method is followed by get() with no intervening set(), get() calls initialValue().
  • void set(T value): Set the value of the calling thread’s storage slot to value.

Listing 4-3 shows how to use ThreadLocal to associate different user IDs with two threads.

After instantiating ThreadLocal and assigning the reference to a volatile class field named userID (the field is volatile because it’s accessed by different threads, which might execute on a multiprocessor/multicore machine—I could have specified final instead), the default main thread creates two more threads that store different java.lang.String objects in userID and output their objects.

Compile Listing 4-3 as follows:

javac ThreadLocalDemo.java

Run the resulting application as follows:

java ThreadLocalDemo

You should observe the following output (possibly not in this order):

A foxtrot
B charlie

Values stored in thread-local variables are not related. When a new thread is created, it gets a new storage slot containing initialValue()’s value. Perhaps you would prefer to pass a value from a parent thread, a thread that creates another thread, to a child thread, the created thread. You accomplish this task with InheritableThreadLocal.

InheritableThreadLocal is a subclass of ThreadLocal. As well as declaring an InheritableThreadLocal() constructor, this class declares the following protected method:

  • T childValue(T parentValue): Calculate the child’s initial value as a function of the parent’s value at the time the child thread is created. This method is called from the parent thread before the child thread is started. The method returns the argument passed to parentValue and should be overridden when another value is desired.

Listing 4-4 shows how to use InheritableThreadLocal to pass a parent thread’s Integer object to a child thread.

After instantiating InheritableThreadLocal and assigning it to a final class field (I could have used volatile instead) named intVal, the default main thread creates a parent thread, which stores a java.lang.Integer object containing 10 in intVal. The parent thread creates a child thread, which accesses intVal and retrieves its parent thread’s Integer object.

Compile Listing 4-4 as follows:

javac InheritableThreadLocalDemo.java

Run the resulting application as follows:

java InheritableThreadLocalDemo

You should observe the following output:

Child 10

Image Note  For more insight into ThreadLocal and how it’s implemented, check out Patson Luk’s “A Painless Introduction to Java’s ThreadLocal Storage” blog post (http://java.dzone.com/articles/painless-introduction-javas-threadlocal-storage).

Timer Framework

It’s often necessary to schedule a task (a unit of work) for one-shot execution (the task runs only once) or for repeated execution at regular intervals. For example, you might schedule an alarm clock task to run only once (perhaps to wake you up in the morning) or schedule a nightly backup task to run at regular intervals. With either kind of task, you might want the task to run at a specific time in the future or after an initial delay.

You can use Thread and related types to build a framework that accomplishes task scheduling. However, Java 1.3 introduced a more convenient and simpler alternative in the form of the java.util.Timer and java.util.TimerTask classes.

Timer lets you schedule TimerTasks for future execution (in a sequential manner) on a background thread, which is known as the task-execution thread. Timer tasks may be scheduled for one-shot execution or for repeated execution at regular intervals.

Listing 4-5 presents an application that demonstrates one-shot execution of a timer task.

Listing 4-5 describes an application whose default main thread first instantiates a TimerTask anonymous subclass, whose overriding run() method outputs an alarm message, and then executes System.exit(0); because the application won’t terminate until the nondaemon task-execution thread terminates. The default main thread then instantiates Timer and invokes its schedule() method with this task as the first argument. The second argument schedules this task for one-shot execution after an initial delay of 2000 milliseconds.

Compile Listing 4-5 as follows:

javac TimerDemo.java

Run the resulting application as follows:

java TimerDemo

You should observe output that’s similar to the following output:

alarm going off

Listing 4-6 presents an application that demonstrates repeated execution at regular intervals of a timer task.

Listing 4-6 describes an application whose default main thread first instantiates a TimerTask anonymous subclass, whose overriding run() method outputs the current time (in milliseconds). The default main thread then instantiates Timer and invokes its schedule() method with this task as the first argument. The second and third arguments schedule this task for repeated execution after no initial delay and every 1000 milliseconds.

Compile Listing 4-6 (javac TimerDemo.java) and run the resulting application (java TimerDemo). You should observe the truncated output here:

1445655847902
1445655848902
1445655849902
1445655850902
1445655851902
1445655852902

Timer in Depth

The previous applications ran their tasks on a nondaemon task-execution thread. Also, one task ran as a one-shot task, whereas the other task ran repeatedly. To understand how these choices were made, you need to learn more about Timer.

Image Note  Timer scales to large numbers of concurrently scheduled timer tasks (thousands of tasks should present no problem). Internally, this class uses a binary heap to represent its timer task queue so that the cost to schedule a timer task is O(log n), where n is the number of concurrently scheduled timer tasks. To learn more about the O( ) notation, check out Wikipedia’s “Big O notation” topic (http://en.wikipedia.org/wiki/Big_O_notation).

Timer declares the following constructors:

  • Timer(): Create a new timer whose task-execution thread doesn’t run as a daemon thread.
  • Timer(boolean isDaemon): Create a new timer whose task-execution thread may be specified to run as a daemon (pass true to isDaemon). A daemon thread is called for scenarios where the timer will be used to schedule repeating “maintenance activities,” which must be performed for as long as the application is running, but shouldn’t prolong the application’s lifetime.
  • Timer(String name): Create a new timer whose task-execution thread has the specified name. The task-execution thread doesn’t run as a daemon thread. This constructor throws java.lang.NullPointerException when name is null.
  • Timer(String name, boolean isDaemon): Create a new timer whose task-execution thread has the specified name and which may run as a daemon thread. This constructor throws NullPointerException when name is null.

Timer also declares the following methods:

  • void cancel(): Terminate this timer, discarding any currently scheduled timer tasks. This method doesn’t interfere with a currently executing timer task (when it exists). After a timer has been terminated, its execution thread terminates gracefully and no more timer tasks may be scheduled on it. (Calling cancel() from within the run() method of a timer task that was invoked by this timer absolutely guarantees that the ongoing task execution is the last task execution that will ever be performed by this timer.) This method may be called repeatedly; the second and subsequent calls have no effect.
  • int purge(): Remove all canceled timer tasks from this timer’s queue and return the number of timer tasks that have been removed. Calling purge() has no effect on the behavior of the timer, but eliminates references to the canceled timer tasks from the queue. When there are no external references to these timer tasks, they become eligible for garbage collection. (Most applications won’t need to call this method, which is designed for use by the rare application that cancels a large number of timer tasks. Calling purge() trades time for space: this method’s runtime may be proportional to n + c * log n, where n is the number of timer tasks in the queue and c is the number of canceled timer tasks.) It’s permissible to call purge() from within a timer task scheduled on this timer.
  • void schedule(TimerTask task, Date time): Schedule task for execution at time. When time is in the past, task is scheduled for immediate execution. This method throws java.lang.IllegalArgumentException when time.getTime() is negative; java.lang.IllegalStateException when task was already scheduled or canceled, the timer was canceled, or the task-execution thread terminated; and NullPointerException when task or time is null.
  • void schedule(TimerTask task, Date firstTime, long period): Schedule task for repeated fixed-delay execution, beginning at firstTime. Subsequent executions take place at approximately regular intervals, separated by period milliseconds. In fixed-delay execution, each execution is scheduled relative to the actual execution time of the previous execution. When an execution is delayed for any reason (such as garbage collection), subsequent executions are also delayed. In the long run, the frequency of execution will generally be slightly lower than the reciprocal of period (assuming the system clock underlying Object.wait(long) is accurate). As a consequence, when the scheduled firstTime value is in the past, task is scheduled for immediate execution. Fixed-delay execution is appropriate for recurring tasks that require “smoothness.” In other words, this form of execution is appropriate for tasks where it’s more important to keep the frequency accurate in the short run than in the long run. This includes most animation tasks, such as blinking a cursor at regular intervals. It also includes tasks wherein regular activity is performed in response to human input, such as automatically repeating a character for as long as a key is held down. This method throws IllegalArgumentException when firstTime.getTime() is negative or period is negative or zero; IllegalStateException when task was already scheduled or canceled, the timer was canceled, or the task-execution thread terminated; and NullPointerException when task or firstTime is null.
  • void schedule(TimerTask task, long delay): Schedule task for execution after delay milliseconds. This method throws IllegalArgumentException when delay is negative or delay + System.currentTimeMillis() is negative; IllegalStateException when task was already scheduled or canceled, the timer was canceled, or the task-execution thread terminated; and NullPointerException when task is null.
  • void schedule(TimerTask task, long delay, long period): Schedule task for repeated fixed-delay execution, beginning after delay milliseconds. Subsequent executions take place at approximately regular intervals separated by period milliseconds. This method throws IllegalArgumentException when delay is negative, delay + System.currentTimeMillis() is negative, or period is negative or zero; IllegalStateException when task was already scheduled or canceled, the timer was canceled, or the task-execution thread terminated; and NullPointerException when task is null.
  • void scheduleAtFixedRate(TimerTask task, Date firstTime, long period): Schedule task for repeated fixed-rate execution, beginning at time. Subsequent executions take place at approximately regular intervals, separated by period milliseconds. In fixed-rate execution, each execution is scheduled relative to the scheduled execution time of the initial execution. When an execution is delayed for any reason (such as garbage collection), two or more executions will occur in rapid succession to “catch up.” In the long run, the frequency of execution will be exactly the reciprocal of period (assuming the system clock underlying Object.wait(long) is accurate). As a consequence, when the scheduled firstTime is in the past, any “missed” executions will be scheduled for immediate “catch up” execution. Fixed-rate execution is appropriate for recurring activities that are sensitive to absolute time (such as ringing a chime every hour on the hour, or running scheduled maintenance every day at a particular time). It’s also appropriate for recurring activities where the total time to perform a fixed number of executions is important, such as a countdown timer that ticks once every second for 10 seconds. Finally, fixed-rate execution is appropriate for scheduling multiple repeating timer tasks that must remain synchronized with respect to one another. This method throws IllegalArgumentException when firstTime.getTime() is negative, or period is negative or zero; IllegalStateException when task was already scheduled or canceled, the timer was canceled, or the task-execution thread terminated; and NullPointerException when task or firstTime is null.
  • void scheduleAtFixedRate(TimerTask task, long delay, long period): Schedule task for repeated fixed-rate execution, beginning after delay milliseconds. Subsequent executions take place at approximately regular intervals, separated by period milliseconds. This method throws IllegalArgumentException when delay is negative, delay + System.currentTimeMillis() is negative, or period is negative or zero; IllegalStateException when task was already scheduled or canceled, the timer was canceled, or the task-execution thread terminated; and NullPointerException when task is null.

After the last live reference to a Timer object goes away and all outstanding timer tasks have completed execution, the timer’s task-execution thread terminates gracefully (and becomes subject to garbage collection). However, this can take arbitrarily long to occur. (By default, the task-execution thread doesn’t run as a daemon thread, so it’s capable of preventing an application from terminating.) When an application wants to terminate a timer’s task-execution thread rapidly, the application should invoke Timer’s cancel() method.

When the timer’s task-execution thread terminates unexpectedly, for example, because its stop() method was invoked (you should never call any of Thread’s stop() methods because they’re inherently unsafe), any further attempt to schedule a timer task on the timer results in IllegalStateException, as if Timer’s cancel() method had been invoked.

TimerTask in Depth

Timer tasks are instances of classes that subclass the abstract TimerTask class, which implements the Runnable interface. When subclassing TimerTask, you override its void run() method to supply the timer task’s code.

Image Note  Timer tasks should complete quickly. When a timer task takes too long to complete, it “hogs” the timer’s task-execution thread, delaying the execution of subsequent timer tasks, which may “bunch up” and execute in rapid succession if and when the offending timer task finally completes.

You can also call the following methods from within the overriding timer task’s run() method:

  • boolean cancel(): Cancel this timer task. When the timer task has been scheduled for one-shot execution and hasn’t yet run or when it hasn’t yet been scheduled, it will never run. When the timer task has been scheduled for repeated execution, it will never run again. (When the timer task is running when this call occurs, the timer task will run to completion, but will never run again.) Calling cancel() from within the run() method of a repeating timer task absolutely guarantees that the timer task won’t run again. This method may be called repeatedly; the second and subsequent calls have no effect. This method returns true when this timer task is scheduled for one-shot execution and hasn’t yet run or when this timer task is scheduled for repeated execution. It returns false when the timer task was scheduled for one-shot execution and has already run, when the timer task was never scheduled, or when the timer task was already canceled. (Loosely speaking, this method returns true when it prevents one or more scheduled executions from taking place.)
  • long scheduledExecutionTime(): Return the scheduled execution time of the most recent actual execution of this timer task. (When this method is invoked while timer task execution is in progress, the return value is the scheduled execution time of the ongoing timer task execution.) This method is typically invoked from within a task’s run() method to determine whether the current execution of the timer task is sufficiently timely to warrant performing the scheduled activity. For example, you would specify code similar to if (System.currentTimeMillis() -scheduledExecutionTime() >= MAX_TARDINESS) return; at the start of the run() method to abort the current timer task execution when it’s not timely. This method is typically not used in conjunction with fixed-delay execution repeating timer tasks because their scheduled execution times are allowed to drift over time and are thus not terribly significant. scheduledExecutionTime() returns the time at which the most recent execution of this timer task was scheduled to occur, in the format returned by java.util.Date.getTime(). The return value is undefined when the timer task has yet to commence its first execution.

EXERCISES

The following exercises are designed to test your understanding of Chapter 4’s content:

  1. Define thread group.
  2. Why might you use a thread group?
  3. Why should you avoid using thread groups?
  4. Why should you be aware of thread groups?
  5. Define thread-local variable.
  6. True or false: If an entry doesn’t exist in the calling thread’s storage slot when the thread calls get(), this method calls initialValue().
  7. How would you pass a value from a parent thread to a child thread?
  8. Identify the classes that form the Timer Framework.
  9. True or false: Timer() creates a new timer whose task-execution thread runs as a daemon thread.
  10. Define fixed-delay execution.
  11. Which methods do you call to schedule a task for fixed-delay execution?
  12. Define fixed-rate execution.
  13. What is the difference between Timer’s cancel() method and TimerTask’s cancel() method?
  14. Create a BackAndForth application that uses Timer and TimerTask to repeatedly move an asterisk forward 20 steps and then backward 20 steps. The asterisk is output via System.out.print().

Summary

The ThreadGroup class describes a thread group, which stores a set of threads. It simplifies thread management by applying method calls to all contained threads. You should avoid using thread groups because the most useful methods are deprecated and because of a race condition.

The ThreadLocal class describes a thread-local variable, which lets you associate per-thread data (such as a user ID) with a thread. It provides a separate storage slot to each thread that accesses the variable. Think of a thread-local variable as a multislot variable in which each thread can store a different value in the same variable. Each thread sees only its value and is unaware of other threads having their own values in this variable. Values stored in thread-local variables are not related. A parent thread can use the InheritableThreadLocal class to pass a value to a child thread.

It’s often necessary to schedule a task for one-shot execution or for repeated execution at regular intervals. Java 1.3 introduced the Timer Framework, which consists of Timer and TimerTask classes, to facilitate working with threads in a timer context.

Chapter 5 introduces the concurrency utilities and presents executors.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.187.113