Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 7 of 8
Caution: The volatile and final keywords cannot appear together in a shared field variable declaration. Any attempt to include both keywords forces the compiler
to report an error.
The visibility problem does not occur when threads use synchronization to access shared field variables. When a thread acquires
a lock, the thread's working-memory copies of shared field variables reload from their main-memory counterparts. Similarly,
when a thread releases a lock, the working-memory copies flush back to the main-memory shared field variables. For example,
in last month's ProdCons2 application, the producer and consumer threads read from/wrote to the writeable shared field variable's main-memory copy because all access to that shared field variable happened within synchronized contexts.
As a result, synchronization allows threads to communicate via shared field variables.
Tip: To ensure that a read/write operation (outside a synchronized context) on either a long-integer shared field variable or
a double-precision floating-point shared field variable succeeds, prefix the shared field variable's declaration with keyword
volatile.
New developers sometimes think volatility replaces synchronization. Although volatility, through keyword volatile, lets you assign values to long-integer or double-precision floating-point shared field variables outside a synchronized
context, volatility cannot replace synchronization. Synchronization lets you group several operations into an indivisible
unit, which you cannot do with volatility. However, because volatility is faster than synchronization, use volatility in situations
where multiple threads must communicate via a single shared field variable.
Sun's Java 2 Platform, Standard Edition (J2SE) SDK 1.2 introduced the java.lang.ThreadLocal class, which developers use to create thread-local variables—ThreadLocal objects that store values on a per-thread basis. Each ThreadLocal object maintains a separate value (such as a user ID) for each thread that accesses the object. Furthermore, a thread manipulates
its own value and can't access other values in the same thread-local variable.
ThreadLocal has three methods:
Object get (): Returns the calling thread's value from the thread-local variable. Because this method is thread-safe, you can call get() from outside a synchronized context.
Object initialValue (): Returns the calling thread's initial value from the thread-local variable. Each thread's first call to either get() or set(Object value) results in an indirect call to initialValue() to initialize that thread's value in the thread-local variable. Because ThreadLocal's default implementation of initialValue() returns null, you must subclass ThreadLocal and override this method to return a nonnull initial value.
void set (Object value): Sets the current thread's value in the thread-local variable to value. Use this method to replace the value that initialValue() returns.
Listing 5 shows you how to use ThreadLocal:
// ThreadLocalDemo1.java
class ThreadLocalDemo1
{
public static void main (String [] args)
{
MyThread mt1 = new MyThread ("A");
MyThread mt2 = new MyThread ("B");
MyThread mt3 = new MyThread ("C");
mt1.start ();
mt2.start ();
mt3.start ();
}
}
class MyThread extends Thread
{
private static ThreadLocal tl =
new ThreadLocal ()
{
protected synchronized Object initialValue ()
{
return new Integer (sernum++);
}
};
private static int sernum = 100;
MyThread (String name)
{
super (name);
}
public void run ()
{
for (int i = 0; i < 10; i++)
System.out.println (getName () + " " + tl.get ());
}
}
ThreadLocalDemo1 creates a thread-local variable. That variable associates a unique serial number with each thread that accesses the thread-local
variable by calling tl.get (). When a thread first calls tl.get (), ThreadLocal's get() method calls the overridden initialValue() method in the anonymous ThreadLocal subclass. The following output results from one program invocation:
A 100
A 100
A 100
A 100
A 100
A 100
A 100
A 100
A 100
A 100
B 101
B 101
B 101
B 101
B 101
B 101
B 101
B 101
B 101
B 101
C 102
C 102
C 102
C 102
C 102
C 102
C 102
C 102
C 102
C 102
The output associates each thread name (A, B, or C) with a unique serial number. If you run this program a second time, you might see a different serial number associate with
a thread name. Though the number differs, it always associates with a single thread name.
Note: To allow multiple threads access to the same ThreadLocal object, ThreadLocalDemo1 uses the static keyword. Without that keyword, each thread accesses its own ThreadLocal object, with each object containing only a value for one thread, not a separate value for each thread. Because thread-local
variables store values on a per-thread basis, failing to use static in a thread-local variable declaration serves little purpose.
An alternative to overriding ThreadLocal's initialValue() method is calling that class's set(Object value) method to provide an initial value:
// ThreadLocalDemo2.java
class ThreadLocalDemo2
{
public static void main (String [] args)
{
MyThread mt1 = new MyThread ("A");
MyThread mt2 = new MyThread ("B");
MyThread mt3 = new MyThread ("C");
mt1.start ();
mt2.start ();
mt3.start ();
}
}
class MyThread extends Thread
{
private static ThreadLocal tl = new ThreadLocal ();
private static int sernum = 100;
MyThread (String name)
{
super (name);
}
public void run ()
{
synchronized ("A")
{
tl.set ("" + sernum++);
}
for (int i = 0; i < 10; i++)
System.out.println (getName () + " " + tl.get ());
}
}
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:
// 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.
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 tasks—TimerTask 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:
void schedule(TimerTask task, Date time): Schedules task for one-time execution at the specified time.
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.
void schedule(TimerTask task, long delay): Schedules task for one-time execution after delay milliseconds pass.
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:
// 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:
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.
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:
// 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:
// 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.
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:
// ThreadDeathDemo.java
class ThreadDeathDemo
{
public static void main (String [] args)
{
MyThreadGroup mtg = new MyThreadGroup ("My Group");
new MyThread (mtg, "My Thread").start ();
}
}
class MyThread extends Thread
{
MyThread (ThreadGroup tg, String name)
{
super (tg, name);
}
public void run ()
{
System.out.println ("About to do something.");
doSomething ();
System.out.println ("Something done.");
}
void doSomething ()
{
doSomethingHelper ();
}
void doSomethingHelper ()
{
throw new MyThreadDeath (MyThreadDeath.REASON2);
}
}
class MyThreadDeath extends ThreadDeath
{
final static int REASON1 = 1;
final static int REASON2 = 2;
final static int REASON3 = 3;
int reason;
MyThreadDeath (int reason)
{
this.reason = reason;
}
}
class MyThreadGroup extends ThreadGroup
{
MyThreadGroup (String name)
{
super (name);
}
public void uncaughtException (Thread t, Throwable e)
{
if (e instanceof MyThreadDeath)
{
reportError (t, e);
cleanup ();
}
super.uncaughtException (t, e);
}
void reportError (Thread t, Throwable e)
{
System.out.print (t.getName () + " unable to do something. Reason: ");
switch (((MyThreadDeath) e).reason)
{
case MyThreadDeath.REASON1:
System.out.println ("First reason.");
break;
case MyThreadDeath.REASON2:
System.out.println ("Second reason.");
break;
case MyThreadDeath.REASON3:
System.out.println ("Third reason.");
}
}
void cleanup ()
{
System.out.println ("Cleaning up");
}
}
ThreadDeathDemo's main thread executes main()'s byte-code instructions, which create a MyThreadGroup object and a MyThread object that groups into MyThreadGroup. The main thread then starts a thread associated with MyThread.
The MyThread thread enters its run() method, where it prints some text and calls the doSomething() method. That method subsequently calls doSomethingHelper(), which throws a MyThreadDeath object. (I subclass ThreadDeath so I can assign a reason for the death of a thread.) At some point, the thrown MyThreadDeath object results in a call to MyThreadGroup's uncaughtException(Thread t, Throwable e) method, which determines if a MyThreadDeath object was thrown. If so, calls are made to methods reportError(Thread t, Throwable e) and cleanup(), to print error information and perform cleanup operations, respectively. When run, ThreadDeathDemo produces the following output: