Thread Miscellany


Atomic Variables and volatile

Java compilers try to optimize as much code as possible. For example, if you assign an initial value to a field outside of a loop:

 private String prefix; void show() throws Exception {    prefix = ":";    for (int i = 0; i < 100; i++) {       System.out.print(prefix + i);       Thread.sleep(1);    } } 

the Java compiler may figure that the value of the prefix field is fixed for the duration of the show method. It may optimize the code to treat the use of prefix within the loop as a constant. Java thus may not bother reading the value of prefix with each iteration through the loop. This is an appropriate assumption, as long as no other code (in a multithread execution) updates the value of prefix at the same time. But if another thread does modify prefix, code in the loop here may never see the changed value.[17]

[17] The actual behavior may depend on your compiler, JVM, and processor configuration.

You can force Java to always read a fresh value for a field by enclosing access to it in a synchronized block. Java uses this awareness that the code could be executing in multiple threads to ensure that it always gets the latest value for all fields contained within. Another way to get Java to read a fresh value is to declare the field as volatile. Doing so essentially tells the compiler that it cannot optimize access to the field.

When you use a shared boolean variable as the conditional for a thread's run loop, you may want to declare the boolean variable as volatile. This will ensure that the while loop reads the updated value of the boolean each time through the loop.

A related consideration is how Java treats common access to fields accessed from multiple threads. Java guarantees that reading or writing a variablewith the exception of a long or double variableis atomic. This means that the smallest possible operation against the variable is to read or write its entire value. It is not possible for one thread to write a portion of the variable while another thread is writing another portion, which would corrupt the variable.

Thread Information

As mentioned earlier, you can obtain the Thread object for the currently executing thread by using the Thread static method currentThread. The Thread object contains a number of useful methods for obtaining information about the thread. Refer to the Java API documentation for a list of these getter and query methods. Additionally, using setName and getName, you can set and retrieve an arbitrary name for the thread.

Shutting Down

The main thread in an executing application is by default a user thread. As long as there is at least one active user thread, your application will continue to execute. In contrast, you can explicitly designate a thread as a daemon thread. If all user threads have terminated, the application will terminate as well, even if daemon threads continue to execute.

The ThreadTest example demonstrates this behavior with respect to user threads. The code executing in main runs in a user thread. Any threads spawned inherit the execution mode (user, or daemon) of the thread that spawned them, so thread t in the example is a user thread.

 public class ThreadTest {    public static void main(String[] args) throws Exception {       Thread t = new Thread() {          public void run() {             while (true)                System.out.print('.');          }       };       t.start();       Thread.sleep(100);    } } 

When you execute ThreadTest, it will not stop until you force termination of the process (Ctrl-c usually works from the command line).

Setting a Thread object to execute as a daemon thread is as simple as sending it the message setDaemon with a parameter value of TRue. You must set the thread execution mode prior to starting the thread. Once a thread has started, you cannot change its execution mode.

 public class ThreadTest {    public static void main(String[] args) throws Exception {       Thread t = new Thread() {          public void run() {             while (true)                System.out.print('.');          }       };       t.setDaemon(true);       t.start();       Thread.sleep(100);    } } 

You can forcibly exit an application, regardless of whether any user threads remain alive or not, by calling the System static method exit. (The Runtime class has an equivalent method.)

 public class ThreadTest {    public static void main(String[] args) throws Exception {       Thread t = new Thread() {          public void run() {             while (true)                System.out.print('.');          }       };       t.start();       Thread.sleep(100);       Runtime.getRuntime().exit(0);       //System.exit(0);  // this will also work    } } 

The exit method requires you to pass it an int value. This value represents the application return code, which you can use to control shell scripts or batch files.

In many applications, such as Swing applications, you will not have control over other threads that are spawned. Swing itself executes code in threads. More often than not, threads end up being initiated as user threads. Accordingly, you may need to use the exit method to terminate a Swing application.

Managing Exceptions

When a run method throws an (unchecked[18]) exception, the thread in which it is running terminates. Additionally, the exception object itself disappears. You may wish to capture the exception, as ServerTest.testException suggests.

[18] Since the Runnable run method signature declares no exceptions, you cannot override it to throw any checked exceptions.

 public void testException() throws Exception {    final String errorMessage = "problem";    Search faultySearch = new Search(URLS[0], "") {       public void execute() {          throw new RuntimeException(errorMessage);       }    };    server.add(faultySearch);    waitForResults(1);    List<String> log = server.getLog();    assertTrue(log.get(0).indexOf(errorMessage) != -1); } private void waitForResults() {    waitForResults(URLS.length); } private void waitForResults(int count) {    long start = System.currentTimeMillis();    while (numberOfResults < count) {       try {Thread.sleep(1); }       catch (InterruptedException e) {}       if (System.currentTimeMillis() - start > TIMEOUT)          fail("timeout");    } } 

The test uses a mock override of the Search execute method to simulate a RuntimeException being thrown. Your expectation is that this exception will be logged to the Server as a failed search. Here is the Server class implementation:

 private void execute(final Search search) {    Thread thread = new Thread(new Runnable() {       public void run() {          log(START_MSG, search);          search.execute();          log(END_MSG, search);          listener.executed(search);          completeLog.addAll(threadLog.get());       }    });    thread.setUncaughtExceptionHandler(       new Thread.UncaughtExceptionHandler() {          public void uncaughtException(Thread th, Throwable thrown) {             completeLog.add(search + " " + thrown.getMessage());             listener.executed(search);          }       }    );    thread.start(); } 

After creating the thread object, but before starting it, you can set an uncaught exception handler on the thread. You create an uncaught exception handler by implementing the method uncaughtException. If the thread throws an exception that is not caught, the uncaughtException method is called. Java passes uncaughtException both the thread and throwable objects.

Thread Groups

Thread groups provide a way to organize threads into arbitrary groups. A thread group can also contain other thread groups, allowing for a containment hierarchy. You can control all threads contained in a thread group as a unit. For example, you can send the message interrupt to a thread group, which will in turn send the interrupt message to all threads contained within the hierarchy.

In Effective Java, Joshua Bloch writes that thread groups "are best viewed as an unsuccessful experiment, and you may simply ignore their existence."[19] They were originally designed to facilitate security management in Java but never satisfactorily fulfilled this purpose.

[19] [Bloch 2001].

Prior to J2SE 5.0, one useful purpose for thread groups was to monitor threads for uncaught exceptions. However, you can now fulfill this need by creating an UncaughtExceptionHandler for a thread (see the previous section, Managing Exceptions).

Atomic Wrappers

Even simple arithmetic operations are not atomic. As of J2SE 5.0, Sun supplies the package java.util.concurrent.atomic. This package includes a dozen Atomic wrapper classes. Each class wraps a primitive type (int, long, or boolean), a reference, or an array. By using an atomic wrapper, you guarantee that operations on the wrapped value are thread safe and work against an up-to-date value (as if you had declared the field volatile).

Here is an example use:

 AtomicInteger i = new AtomicInteger(50); assertEquals(55, i.addAndGet(5)); assertEquals(55, i.get()); 

Refer to the Java API documentation for further details.



Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net