Chapter    8

Exploring the Basic APIs Part 2

The standard class library’s java.lang package provides many basic APIs, which are designed to support language features. You encountered APIs for mathematics, string management, and packages in the previous chapter. In this chapter I introduce you to those basic library APIs that pertain to the primitive type wrapper classes, threads, and system capabilities.

Exploring the Primitive Type Wrapper Classes

The java.lang package includes Boolean, Byte, Character, Double, Float, Integer, Long, and Short. These classes are known as primitive type wrapper classes because their instances wrap themselves around values of primitive types.

Note   The primitive type wrapper classes are also known as value classes.

Java provides these eight primitive type wrapper classes for two reasons:

  • The Collections Framework (discussed in Chapter 9) provides lists, sets, and maps that can only store objects; they cannot store primitive-type values. You store a primitive-type value in a primitive type wrapper class instance and store the instance in the collection.
  • These classes provide a good place to associate useful constants (such as MAX_VALUE and MIN_VALUE) and class methods (such as Integer’s parseInt() methods and Character’s isDigit(), isLetter(), and toUpperCase() methods) with the primitive types.

In this section I introduce you to each of these primitive type wrapper classes and a java.lang class named Number.

Boolean

Boolean is the smallest of the primitive type wrapper classes. This class declares three constants, including TRUE and FALSE, which denote precreated Boolean objects. It also declares a pair of constructors for initializing a Boolean object:

  • Boolean(boolean value) initializes the Boolean object to value.
  • Boolean(String s) converts s’s text to a true or false value and stores this value in the Boolean object.

The second constructor compares s’s value with true. Because the comparison is case insensitive, any uppercase/lowercase combination of these four letters (such as true, TRUE, or tRue) results in true being stored in the object. Otherwise, the constructor stores false in the object.

Note   Boolean’s constructors are complemented by boolean booleanValue(), which returns the wrapped Boolean value.

Boolean also declares or overrides the following methods:

  • int compareTo(Boolean b) compares the current Boolean object with b to determine their relative order. The method returns 0 when the current object contains the same Boolean value as b, a positive value when the current object contains true and b contains false, and a negative value when the current object contains false and b contains true.
  • boolean equals(Object o) compares the current Boolean object with o and returns true when o is not null, o is of type Boolean, and both objects contain the same Boolean value.
  • static boolean getBoolean(String name) returns true when a system property (discussed later in this chapter) identified by name exists and is equal to true.
  • int hashCode() returns a suitable hash code that allows Boolean objects to be used with hash-based collections (discussed in Chapter 9).
  • static boolean parseBoolean(String s) parses s, returning true when s equals "true", "TRUE", "True", or any other uppercase/lowercase combination. Otherwise, this method returns false. (Parsing breaks a sequence of characters into meaningful components, known as tokens.)
  • String toString() returns "true" when the current Boolean instance contains true; otherwise, this method returns "false".
  • static String toString(boolean b) returns "true" when b contains true; otherwise, this method returns "false".
  • static Boolean valueOf(boolean b) returns TRUE when b contains true or FALSE when b contains false.
  • static Boolean valueOf(String s) returns TRUE when s equals "true", "TRUE", "True", or any other uppercase/lowercase combination. Otherwise, this method returns FALSE.

Caution   Newcomers to the Boolean class often think that getBoolean() returns a Boolean object’s true/false value. However, getBoolean() returns the value of a Boolean-based system property—I discuss system properties later in this chapter. If you need to return a Boolean object’s true/false value, use the booleanValue() method instead.

It’s often better to use TRUE and FALSE than to create Boolean objects. For example, suppose you need a method that returns a Boolean object containing true when the method’s double argument is negative, or false when this argument is zero or positive. You might declare your method like the following isNegative() method:

Boolean isNegative(double d)
{
   return new Boolean(d < 0);
}

Although this method is concise, it unnecessarily creates a Boolean object. When the method is called frequently, many Boolean objects are created that consume heap space. When heap space runs low, the garbage collector runs and slows down the application, which impacts performance.

The following example reveals a better way to code isNegative():

Boolean isNegative(double d)
{
   return (d < 0) ? Boolean.TRUE : Boolean.FALSE;
}

This method avoids creating Boolean objects by returning either the precreated TRUE or FALSE object.

Tip   You should strive to create as few objects as possible. Not only will your applications have smaller memory footprints, they’ll perform better because the garbage collector will not run as often.

Character

Character is the largest of the primitive type wrapper classes, containing many constants, a constructor, many methods, and a pair of nested classes (Subset and UnicodeBlock).

Note   Character’s complexity derives from Java’s support for Unicode (http://en.wikipedia.org/wiki/Unicode). For brevity, I ignore much of Character’s Unicode-related complexity, which is beyond the scope of this chapter.

Character declares a single Character(char value) constructor, which you use to initialize a Character object to value. This constructor is complemented by char charValue(), which returns the wrapped character value.

When you start writing applications, you might codify expressions such as ch >= '0' && ch <= '9' (test ch to see if it contains a digit) and ch >= 'A' && ch <= 'Z' (test ch to see if it contains an uppercase letter). You should avoid doing so for three reasons:

  • It’s too easy to introduce a bug into the expression. For example, ch > '0' && ch <= '9' introduces a subtle bug that doesn’t include '0' in the comparison.
  • The expressions are not very descriptive of what they are testing.
  • The expressions are biased toward Latin digits (0–9) and letters (A–Z and a–z). They don’t take into account digits and letters that are valid in other languages. For example, 'u0beb' is a character literal representing one of the digits in the Tamil language.

Character declares several comparison and conversion class methods that address these concerns. These methods include the following:

  • static boolean isDigit(char ch) returns true when ch contains a digit (typically 0 through 9 but also digits in other alphabets).
  • static boolean isLetter(char ch) returns true when ch contains a letter (typically A–Z or a–z but also letters in other alphabets).
  • static boolean isLetterOrDigit(char ch) returns true when ch contains a letter or digit (typically A–Z, a–z, or 0–9 but also letters or digits in other alphabets).
  • static boolean isLowerCase(char ch ) returns true when ch contains a lowercase letter.
  • static boolean isUpperCase(char ch) returns true when ch contains an uppercase letter.
  • static boolean isWhitespace(char ch) returns true when ch contains a whitespace character (typically a space, a horizontal tab, a carriage return, or a line feed).
  • static char toLowerCase(char ch) returns the lowercase equivalent of ch’s uppercase letter; otherwise, this method returns ch’s value.
  • static char toUpperCase(char ch ) returns the uppercase equivalent of ch’s lowercase letter; otherwise, this method returns ch’s value.

For example, isDigit(ch) is preferable to ch >= '0' && ch <= '9' because it avoids a source of bugs, is more readable, and returns true for non-Latin digits (e.g., 'u0beb') as well as Latin digits.

Float and Double

Float and Double store floating-point and double precision floating-point values in Float and Double objects, respectively. These classes declare the following constants:

  • MAX_VALUE identifies the maximum value that can be represented as a float or double.
  • MIN_VALUE identifies the minimum value that can be represented as a float or double.
  • NaN represents 0.0F/0.0F as a float and 0.0/0.0 as a double.
  • NEGATIVE_INFINITY represents -infinity as a float or double.
  • POSITIVE_INFINITY represents +infinity as a float or double.

Float and Double also declare the following constructors for initializing their objects:

  • Float(float value) initializes the Float object to value.
  • Float(double value) initializes the Float object to the float equivalent of value.
  • Float(String s) converts s’s text to a floating-point value and stores this value in the Float object.
  • Double(double value) initializes the Double object to value.
  • Double(String s ) converts s’s text to a double precision floating-point value and stores this value in the Double object.

Float’s constructors are complemented by float floatValue(), which returns the wrapped floating-point value. Similarly, Double’s constructors are complemented by double doubleValue(), which returns the wrapped double precision floating-point value.

Float declares several utility methods in addition to floatValue(). These methods include the following:

  • static int floatToIntBits(float value) converts value to a 32-bit integer.
  • static boolean isInfinite(float f) returns true when f’s value is +infinity or –infinity. A related boolean isInfinite() method returns true when the current Float object’s value is +infinity or −infinity.
  • static boolean isNaN(float f) returns true when f’s value is NaN. A related boolean isNaN() method returns true when the current Float object’s value is NaN.
  • static float parseFloat(String s) parses s, returning the floating-point equivalent of s’s textual representation of a floating-point value or throwing java.lang.NumberFormatException when this representation is invalid (contains letters, for example).

Double declares several utility methods as well as doubleValue(). These methods include the following:

  • static long doubleToLongBits(double value) converts value to a long integer.
  • static boolean isInfinite(double d) returns true when d’s value is +infinity or −infinity. A related boolean isInfinite() method returns true when the current Double object’s value is +infinity or −infinity.
  • static boolean isNaN(double d) returns true when d’s value is NaN. A related public boolean isNaN() method returns true when the current Double object’s value is NaN.
  • static double parseDouble(String s) parses s, returning the double precision floating-point equivalent of s’s textual representation of a double precision floating-point value or throwing NumberFormatException when this representation is invalid.

The floatToIntBits() and doubleToIntBits() methods are used in implementations of the equals() and hashCode() methods that must take float and double fields into account. floatToIntBits() and doubleToIntBits() allow equals() and hashCode() to respond properly to the following situations:

  • equals() must return true when f1 and f2 contain Float.NaN (or d1 and d2 contain Double.NaN). If equals() was implemented in a manner similar to f1.floatValue() == f2.floatValue() (or d1.doubleValue() == d2.doubleValue()), this method would return false because NaN is not equal to anything, including itself.
  • equals() must return false when f1 contains +0.0 and f2 contains –0.0 (or vice versa), or d1 contains +0.0 and d2 contains -0.0 (or vice versa). If equals() was implemented in a manner similar to f1.floatValue() == f2.floatValue() (or d1.doubleValue() == d2.doubleValue()), this method would return true because +0.0 == −0.0 returns true.

These requirements are needed for hash-based collections (discussed in Chapter 9) to work properly. Listing 8-1 shows how they impact Float’s and Double’s equals() methods.

Listing 8-1.  Demonstrating Float’s equals() Method in a NaN Context and Double’s equals() Method in a +/-0.0 Context

public class FloatDoubleDemo
{
   public static void main(String[] args)
   {
      Float f1 = new Float(Float.NaN);
      System.out.println(f1.floatValue());
      Float f2 = new Float(Float.NaN);
      System.out.println(f2.floatValue());
      System.out.println(f1.equals(f2));
      System.out.println(Float.NaN == Float.NaN);
      System.out.println();
      Double d1 = new Double(+0.0);
      System.out.println(d1.doubleValue());
      Double d2 = new Double(-0.0);
      System.out.println(d2.doubleValue());
      System.out.println(d1.equals(d2));
      System.out.println(+0.0 == -0.0);
   }
}

Compile Listing 8-1 (javac FloatDoubleDemo.java) and run this application (java FloatDoubleDemo). The following output proves that Float’s equals() method properly handles NaN and Double’s equals() method properly handles +/-0.0:

NaN
NaN
true
false

0.0
-0.0
false
true

Tip   When you want to test a float or double value for equality with +infinity or −infinity (but not both), don’t use isInfinite(). Instead, compare the value with NEGATIVE_INFINITY or POSITIVE_INFINITY via ==. For example, f == Float.NEGATIVE_INFINITY.

You will find parseFloat() and parseDouble() useful in many contexts. For example, Listing 8-2 uses parseDouble() to parse command-line arguments into doubles.

Listing 8-2.  Parsing Command-Line Arguments into Double Precision Floating-Point Values

public class Calc
{
   public static void main(String[] args)
   {
      if (args.length != 3)
      {
         System.err.println("usage: java Calc value1 op value2");
         System.err.println("op is one of +, -, x, or /");
         return;
      }
      try
      {
         double value1 = Double.parseDouble(args[0]);
         double value2 = Double.parseDouble(args[2]);
         if (args[1].equals("+"))
            System.out.println(value1 + value2);
         else
         if (args[1].equals("-"))
            System.out.println(value1 - value2);
         else
         if (args[1].equals("x"))
            System.out.println(value1 * value2);
         else
         if (args[1].equals("/"))
            System.out.println(value1 / value2);
         else
            System.err.println("invalid operator: " + args[1]);
      }
      catch (NumberFormatException nfe)
      {
         System.err.println("Bad number format: " + nfe.getMessage());
      }
   }
}

Specify java Calc 10E+3 + 66.0 to try out the Calc application. This application responds by outputting 10066.0. If you specified java Calc 10E+3 + A instead, you would observe Bad number format: For input string: "A" as the output, which is in response to the second parseDouble() method call’s throwing of a NumberFormatException object.

Although NumberFormatException describes an unchecked exception, and although unchecked exceptions are often not handled because they represent coding mistakes, NumberFormatException doesn’t fit this pattern in this example. The exception doesn’t arise from a coding mistake; it arises from someone passing an illegal numeric argument to the application, which cannot be avoided through proper coding. Perhaps NumberFormatException should have been implemented as a checked exception.

Integer, Long, Short, and Byte

Integer, Long, Short, and Byte store 32-bit, 64-bit, 16-bit, and 8-bit integer values in Integer, Long, Short, and Byte objects, respectively.

Each class declares MAX_VALUE and MIN_VALUE constants that identify the maximum and minimum values that can be represented by its associated primitive type. These classes also declare the following constructors for initializing their objects:

  • Integer(int value) initializes the Integer object to value.
  • Integer(String s) converts s’s text to a 32-bit integer value and stores this value in the Integer object.
  • Long(long value) initializes the Long object to value.
  • Long(String s) converts s’s text to a 64-bit integer value and stores this value in the Long object.
  • Short(short value) initializes the Short object to value.
  • Short(String s) converts s’s text to a 16-bit integer value and stores this value in the Short object.
  • Byte(byte value ) initializes the Byte object to value.
  • Byte(String s) converts s’s text to an 8-bit integer value and stores this value in the Byte object.

Integer’s constructors are complemented by int intValue(), Long’s constructors are complemented by long longValue(), Short’s constructors are complemented by short shortValue(), and Byte’s constructors are complemented by byte byteValue(). These methods return wrapped integers.

These classes declare various useful integer-oriented methods. For example, Integer declares the following class methods for converting a 32-bit integer to a String according to a specific representation (binary, hexadecimal, octal, and decimal):

  • static String toBinaryString(int i) returns a String object containing i’s binary representation. For example, Integer.toBinaryString(255) returns a String object containing 11111111.
  • static String toHexString(int i) returns a String object containing i’s hexadecimal representation. For example, Integer.toHexString(255) returns a String object containing ff.
  • static String toOctalString(int i) returns a String object containing i’s octal representation. For example, toOctalString(64) returns a String object containing 100.
  • static String toString(int i) returns a String object containing i’s decimal representation. For example, toString(255) returns a String object containing 255.

It’s often convenient to prepend zeros to a binary string so that you can align multiple binary strings in columns. For example, you might want to create an application that displays the following aligned output:

11110001
+
00000111
--------
11111000

Unfortunately, toBinaryString() doesn’t let you accomplish this task. For example, Integer.toBinaryString(7) returns a String object containing 111 instead of 00000111. Listing 8-3’s toAlignedBinaryString() method addresses this oversight.

Listing 8-3.  Aligning Binary Strings

public class AlignBinaryString
{
   public static void main(String[] args)
   {
      System.out.println(toAlignedBinaryString(7, 8));
      System.out.println(toAlignedBinaryString(255, 16));
      System.out.println(toAlignedBinaryString(255, 7));
   }

   static String toAlignedBinaryString(int i, int numBits)
   {
      String result = Integer.toBinaryString(i);
      if (result.length() > numBits)
         return null; // cannot fit result into numBits columns
      int numLeadingZeros = numBits - result.length();
      StringBuilder sb = new StringBuilder();
      for (int j = 0; j < numLeadingZeros; j++)
         sb.append('0'),
      return sb.toString() + result;
   }
}

The toAlignedBinaryString() method takes two arguments: the first argument specifies the 32-bit integer that is to be converted into a binary string, and the second argument specifies the number of bit columns in which to fit the string.

After calling toBinaryString() to return i’s equivalent binary string without leading zeros, toAlignedBinaryString() verifies that the string’s digits can fit into the number of bit columns specified by numBits. If they don’t fit, this method returns null.

Moving on, toAlignedBinaryString() calculates the number of leading "0"s to prepend to result and then uses a for loop to create a string of leading zeros. This method ends by returning the leading zeros string prepended to the result string.

When you run this application, it generates the following output:

00000111
0000000011111111
null

Number

Each of Float, Double, Integer, Long, Short, and Byte provides the other classes’ x Value() methods in addition to its own x Value() method. For example, Float provides doubleValue(), intValue(), longValue(), shortValue(), and byteValue() as well as floatValue().

All six methods are members of Number, which is the abstract superclass of Float, Double, Integer, Long, Short, and ByteNumber’s floatValue(), doubleValue(), intValue(), and longValue() methods are abstract. Number is also the superclass of java.math.BigDecimal and java.math.BigInteger (and some concurrency-related classes; see Chapter 10).

Number exists to simplify iterating over a collection of Number subclass objects. For example, you can declare a variable of java.util.List<Number> type and initialize it to an instance of java.util.ArrayList<Number>. You can then store a mixture of Number subclass objects in the collection, and iterate over this collection by calling a subclass method polymorphically.

Exploring Threads

Applications execute via threads, which are independent paths of execution through an application’s code. When multiple threads are executing, each thread’s path can differ from other thread paths. For example, a thread might execute one of a switch statement’s cases, and another thread might execute another of this statement’s cases.

Note   Applications use threads to improve performance. Some applications can get by with only the default main thread (the thread that executes the main() method) to carry out their tasks, but other applications need additional threads to perform time-intensive tasks in the background so that they remain responsive to their users.

The virtual machine gives each thread its own method-call stack to prevent threads from interfering with each other. Separate stacks let threads keep track of their next instructions to execute, which can differ from thread to thread. The stack also provides a thread with its own copy of method parameters, local variables, and return value.

Java supports threads via its Threads API. This API consists of one interface (Runnable) and four classes (Thread, ThreadGroup, ThreadLocal, and InheritableThreadLocal) in the java.lang package. After exploring Runnable and Thread (and mentioning ThreadGroup during this exploration), in this section I explore thread synchronization, ThreadLocal, and InheritableThreadLocal.

Note   Java 5 introduced the java.util.concurrent package as a high-level alternative to the low-level Threads API. (I will discuss this package in Chapter 10.) Although java.util.concurrent is the preferred API for working with threads, you should also be somewhat familiar with Threads because it’s helpful in simple threading scenarios. Also, you might have to analyze someone else’s source code that depends on Threads.

Runnable and Thread

Java provides the Runnable interface to identify those objects that supply code for threads to execute via this interface’s solitary void run() method—a thread receives no arguments and returns no value. Classes implement Runnable to supply this code, and one of these classes is Thread.

Thread provides a consistent interface to the underlying operating system’s threading architecture. (The operating system is typically responsible for creating and managing threads.) Thread makes it possible to associate code with threads as well as start and manage those threads. Each Thread instance associates with a single thread.

Thread declares several constructors for initializing Thread objects. Some of these constructors take Runnable arguments: you can supply code to run without having to extend Thread. Other constructors don’t take Runnable arguments: you must extend Thread and override its run() method to supply the code to run.

For example, Thread(Runnable runnable) initializes a new Thread object to the specified runnable whose code is to be executed. In contrast, Thread() doesn’t initialize Thread to a Runnable argument. Instead, your Thread subclass provides a constructor that calls Thread(), and the subclass also overrides Thread’s run() method.

In the absence of an explicit name argument, each constructor assigns a unique default name (starting with Thread-) to the Thread object. Names make it possible to differentiate threads. In contrast to the previous two constructors, which choose default names, Thread(String threadName) lets you specify your own thread name.

Thread also declares methods for starting and managing threads. Table 8-1 describes many of the more useful methods.

Table 8-1 .  Thread Methods

Method Description
static Thread currentThread() Return the Thread object associated with the thread that calls this method.
String getName() Return the name associated with this Thread object.
Thread.State getState() Return the state of the thread associated with this Thread object. The state is identified by the Thread.State enum as one of BLOCKED (waiting to acquire a lock, discussed later), NEW (created but not started), RUNNABLE (executing), TERMINATED (the thread has died), TIMED_WAITING (waiting for a specified amount of time to elapse), or WAITING (waiting indefinitely).
void interrupt() Set the interrupt status flag in this Thread object. If the associated thread is blocked or is waiting, clear this flag and wake up the thread by throwing an instance of the java.lang.InterruptedException class.
static boolean interrupted() Return true when the thread associated with this Thread object has a pending interrupt request. Clear the interrupt status flag.
boolean isAlive() Return true to indicate that this Thread object’s associated thread is alive and not dead. A thread’s life span ranges from just before it is actually started within the start() method to just after it leaves the run() method, at which point it dies.
boolean isDaemon() Return true when the thread associated with this Thread object is a daemon thread, a thread that acts as a helper to a user thread (nondaemon thread) and dies automatically when the application’s last nondaemon thread dies so the application can exit.
boolean isInterrupted() Return true when the thread associated with this Thread object has a pending interrupt request.
void join() The thread that calls this method on this Thread object waits for the thread associated with this object to die. This method throws InterruptedException when this Thread object’s interrupt() method is called.
void join(long millis) The thread that calls this method on this Thread object waits for the thread associated with this object to die, or until millis milliseconds have elapsed, whichever happens first. This method throws InterruptedException when this Thread object’s interrupt() method is called.
void setDaemon(boolean isDaemon) Mark this Thread object’s associated thread as a daemon thread when isDaemon is true. This method throws java.lang.IllegalThreadStateException when the thread has not yet been created and started.
void setName(String threadName) Assign threadName’s value to this Thread object as the name of its associated thread.
static void sleep(long time) Pause the thread associated with this Thread object for time milliseconds. This method throws InterruptedException when this Thread object’s interrupt() method is called while the thread is sleeping.
void start() Create and start this Thread object’s associated thread. This method throws IllegalThreadStateException when the thread was previously started and is running or has died.

Listing 8-4 introduces you to the Threads API via a main() method that demonstrates Runnable, Thread(Runnable runnable), currentThread(), getName(), and start().

Listing 8-4.  A Pair of Counting Threads

public class CountingThreads
{
   public static void main(String[] args)
   {
      Runnable r = new Runnable()
                   {
                      @Override
                      public void run()
                      {
                         String name = Thread.currentThread().getName();
                         int count = 0;
                         while (true)
                            System.out.println(name + ": " + count++);
                      }
                   };
      Thread thdA = new Thread(r);
      Thread thdB = new Thread(r);
      thdA.start();
      thdB.start();
   }
}

According to Listing 8-4, the default main thread that executes main() first instantiates an anonymous class that implements Runnable. It then creates two Thread objects, initializing each object to the runnable, and calls Thread’s start() method to create and start both threads. After completing these tasks, the main thread exits main() and dies.

Each of the two started threads executes the runnable’s run() method. It calls Thread’s currentThread() method to obtain its associated Thread instance, uses this instance to call Thread’s getName() method to return its name, initializes count to 0, and enters an infinite loop where it outputs name and count and increments count on each iteration.

Tip   To stop an application that doesn’t end, press the Ctrl and C keys simultaneously on a Windows platform or do the equivalent on a non-Windows platform.

I observe both threads alternating in their execution when I run this application on the 64-bit Windows 7 platform. Partial output from one run appears here:

Thread-0: 0
Thread-0: 1
Thread-1: 0
Thread-0: 2
Thread-1: 1
Thread-0: 3
Thread-1: 2
Thread-0: 4
Thread-1: 3
Thread-0: 5
Thread-1: 4
Thread-0: 6
Thread-1: 5
Thread-0: 7
Thread-1: 6
Thread-1: 7
Thread-1: 8
Thread-1: 9
Thread-1: 10
Thread-1: 11
Thread-1: 12

Note   I executed java CountThreads >output.txt to capture the output to output.txt and then presented part of this file’s content previously. Capturing output to a file may significantly affect the output that would otherwise be observed if output wasn’t captured. Because I present captured thread output throughout this section, bear this in mind when executing the application on your platform. Also, note that your platform’s threading architecture may impact the observable results. I’ve tested each example in this section on the 64-bit Windows 7 platform.

When a computer has enough processors and/or processor cores, the computer’s operating system assigns a separate thread to each processor or core so the threads execute concurrently (at the same time). When a computer doesn’t have enough processors and/or cores, a thread must wait its turn to use the shared processor/core.

The operating system uses a scheduler (http://en.wikipedia.org/wiki/Scheduling_(computing)) to determine when a waiting thread executes. The following list identifies three different schedulers:

Caution   Although the previous output indicates that the first thread (Thread-0) starts executing, never assume that the thread associated with the Thread object whose start() method is called first is the first thread to execute. Although this might be true of some schedulers, it might not be true of others.

A multilevel feedback queue and many other thread schedulers take the concept of priority (thread relative importance) into account. They often combine preemptive scheduling (higher priority threads preempt—interrupt and run instead of—lower priority threads) with round robin scheduling (equal priority threads are given equal slices of time, which are known as time slices, and take turns executing).

Thread supports priority via its void setPriority(int priority) method (set the priority of this Thread object’s thread to priority, which ranges from Thread.MIN_PRIORITY to Thread.MAX_PRIORITYThread.NORMAL_PRIORITY identifies the default priority) and int getPriority() method (return the current priority).

Caution   Using the setPriority() method can impact an application’s portability across platforms because different schedulers can handle a priority change in different ways. For example, one platform’s scheduler might delay lower priority threads from executing until higher priority threads finish. This delaying can lead to indefinite postponement or starvation because lower priority threads “starve” while waiting indefinitely for their turn to execute, and this can seriously hurt the application’s performance. Another platform’s scheduler might not indefinitely delay lower priority threads, improving application performance.

Listing 8-5 refactors Listing 8-4’s main() method to give each thread a nondefault name and to put each thread to sleep after outputting name and count.

Listing 8-5.  A Pair of Counting Threads Revisited

public class CountingThreads
{
   public static void main(String[] args)
   {
      Runnable r = new Runnable()
                   {
                      @Override
                      public void run()
                      {
                         String name = Thread.currentThread().getName();
                         int count = 0;
                         while (true)
                         {
                            System.out.println(name + ": " + count++);
                            try
                            {
                               Thread.sleep(100);
                            }
                            catch (InterruptedException ie)
                            {
                            }
                         }
                      }
                   };
      Thread thdA = new Thread(r);
      thdA.setName("A");
      Thread thdB = new Thread(r);
      thdB.setName("B");
      thdA.start();
      thdB.start();
   }
}

Listing 8-5 reveals that threads A and B execute Thread.sleep(100); to sleep for 100 milliseconds. This sleep results in each thread executing more frequently, as the following partial output reveals:

A: 0
B: 0
A: 1
B: 1
B: 2
A: 2
B: 3
A: 3
B: 4
A: 4
B: 5
A: 5
B: 6
A: 6
B: 7
A: 7

A thread will occasionally start another thread to perform a lengthy calculation, download a large file, or perform some other time-consuming activity. After finishing its other tasks, the thread that started the worker thread is ready to process the results of the worker thread and waits for the worker thread to finish and die.

It’s possible to wait for the worker thread to die by using a while loop that repeatedly calls Thread’s isAlive() method on the worker thread’s Thread object and sleeps for a certain length of time when this method returns true. However, Listing 8-6 demonstrates a less verbose alternative: the join() method.

Listing 8-6.  Joining the Default Main Thread with a Background Thread

public class JoinDemo
{
   public static void main(String[] args)
   {
      Runnable r = new Runnable()
                   {
                      @Override
                      public void run()
                      {
                         System.out.println("Worker thread is simulating " +
                                            "work by sleeping for 5 seconds.");
                         try
                         {
                            Thread.sleep(5000);
                         }
                         catch (InterruptedException ie)
                         {
                         }
                         System.out.println("Worker thread is dying.");
                      }
                   };
      Thread thd = new Thread(r);
      thd.start();
      System.out.println("Default main thread is doing work.");
      try
      {
         Thread.sleep(2000);
      }
      catch (InterruptedException ie)
      {
      }
      System.out.println("Default main thread has finished its work.");
      System.out.println("Default main thread is waiting for worker thread " +
                         "to die.");
      try
      {
         thd.join();
      }
      catch (InterruptedException ie)
      {
      }
      System.out.println("Main thread is dying.");
   }
}

Listing 8-6 demonstrates the default main thread starting a worker thread, performing some work, and then waiting for the worker thread to die by calling join() via the worker thread’s thd object. When you run this application, you will discover output similar to the following (message order might differ somewhat):

Default main thread is doing work.
Worker thread is simulating work by sleeping for 5 seconds.
Default main thread has finished its work.
Default main thread is waiting for worker thread to die.
Worker thread is dying.
Main thread is dying.

Every Thread object belongs to some ThreadGroup object; Thread declares a ThreadGroup getThreadGroup() method that returns this object. You should ignore thread groups because they are not that useful. If you need to logically group Thread objects, you should use an array or collection instead.

Caution   Various ThreadGroup methods are flawed. For example, int enumerate(Thread[] threads) will not include all active threads in its enumeration when its threads array argument is too small to store their Thread objects. Although you might think that you could use the return value from the int activeCount() method to properly size this array, there is no guarantee that the array will be large enough because activeCount()’s return value fluctuates with the creation and death of threads.

However, you should still know about ThreadGroup because of its contribution in handling exceptions that are thrown while a thread is executing. Listing 8-7 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 instance.

Listing 8-7.  Throwing an Exception from the run() Method

public class ExceptionThread
{
   public static void main(String[] args)
   {
      Runnable r = new Runnable()
                   {
                      @Override
                      public void run()
                      {
                         int x = 1 / 0; // Line 10
                      }
                   };
      Thread thd = new Thread(r);
      thd.start();
   }
}

Run this application and you will see an exception trace that identifies the thrown ArithmeticException:

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

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

  • The virtual machine 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 instance was thrown. If this method throws an exception/error, the exception/error is ignored by the virtual machine.
  • Assuming that setUncaughtExceptionHandler() was not called to install a handler, the virtual machine 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 is an instance of java.lang.ThreadDeath. If so, nothing special is done. Otherwise, as Listing 8-7’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 8-8 demonstrates Thread’s setUncaughtExceptionHandler() and setDefaultUncaughtExceptionHandler() methods.

Listing 8-8.  Demonstrating Uncaught Exception Handlers

public class ExceptionThread
{
   public static void main(String[] args)
   {
      Runnable r = new Runnable()
                   {
                      @Override
                      public void run()
                      {
                         int x = 1 / 0;
                      }
                   };
      Thread thd = new Thread(r);
      Thread.UncaughtExceptionHandler uceh;
      uceh = new Thread.UncaughtExceptionHandler()
             {
                @Override
                public void uncaughtException(Thread t, Throwable e)
                {
                   System.out.println("Caught throwable " + e + " for thread "
                                      + t);
                }
             };
      thd.setUncaughtExceptionHandler(uceh);
      uceh = new Thread.UncaughtExceptionHandler()
             {
                @Override
                public void uncaughtException(Thread t, Throwable e)
                {
                   System.out.println("Default uncaught exception handler");
                   System.out.println("Caught throwable " + e + " for thread "
                                      + t);
                }
             };
      thd.setDefaultUncaughtExceptionHandler(uceh);
      thd.start();
   }
}

When you run this application, you will observe the following 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 is not called. To see that output, you must comment out thd.setUncaughtExceptionHandler(uceh) ;. If you also comment out thd.setDefaultUncaughtExceptionHandler(uceh);, you will see Listing 8-7’s output.

Caution   Thread declares several deprecated methods, including stop() (stop an executing thread). These methods have been deprecated because they are unsafe. Do not use these deprecated methods. (I will show you how to safely stop a thread later in this chapter.) Also, you should avoid the static void yield() method, which is intended to switch execution from the current thread to another thread, because it can affect portability and hurt application performance. Although yield() might switch to another thread on some platforms (which can improve performance), yield() might only return to the current thread on other platforms (which hurts performance because the yield() call has only wasted time).

Thread Synchronization

Throughout its execution, each thread is isolated from other threads because it has been given its own method-call stack. However, threads can still interfere with each other when they access and manipulate shared data. This interference can corrupt the shared data, and this corruption can cause an application to fail.

For example, consider a checking account in which a husband and wife have joint access. Suppose that the husband and wife decide to empty this account at the same time without knowing that the other is doing the same thing. Listing 8-9 demonstrates this scenario.

Listing 8-9.  A Problematic Checking Account

public class CheckingAccount
{
   private int balance;

   public CheckingAccount(int initialBalance)
   {
      balance = initialBalance;
   }

   public boolean withdraw(int amount)
   {
      if (amount <= balance)
      {
         try
         {
            Thread.sleep((int) (Math.random() * 200));
         }
         catch (InterruptedException ie)
         {
         }
         balance -= amount;
         return true;
      }
      return false;
   }

   public static void main(String[] args)
   {
      final CheckingAccount ca = new CheckingAccount(100);
      Runnable r = new Runnable()
                   {
                      public void run()
                      {
                         String name = Thread.currentThread().getName();
                         for (int i = 0; i < 10; i++)
                             System.out.println (name + " withdraws $10: " +
                                                 ca.withdraw(10));
                      }
                   };
      Thread thdHusband = new Thread(r);
      thdHusband.setName("Husband");
      Thread thdWife = new Thread(r);
      thdWife.setName("Wife");
      thdHusband.start();
      thdWife.start();
   }
}

This application lets more money be withdrawn than is available in the account. For example, the following output reveals $110 being withdrawn when only $100 is available:

Wife withdraws $10: true
Husband withdraws $10: true
Husband withdraws $10: true
Wife withdraws $10: true
Wife withdraws $10: true
Husband withdraws $10: true
Wife withdraws $10: true
Wife withdraws $10: true
Wife withdraws $10: true
Husband withdraws $10: true
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Husband withdraws $10: false
Wife withdraws $10: true
Wife withdraws $10: false
Wife withdraws $10: false
Wife withdraws $10: false

The reason why more money is withdrawn than is available for withdrawal is that a race condition exists between the husband and wife threads.

Note   A race condition is a scenario in which multiple threads update the same object at the same time or nearly at the same time. Part of the object stores values written to it by one thread, and another part of the object stores values written to it by another thread.

The race condition exists because the actions of checking the amount for withdrawal to ensure that it is less than what appears in the balance and deducting the amount from the balance are not atomic (indivisible) operations. (Although atoms are divisible, atomic is commonly used to refer to something being indivisible.)

Note   The Thread.sleep() method call that sleeps for a variable amount of time (up to a maximum of 199 milliseconds) is present so that you can observe more money being withdrawn than is available for withdrawal. Without this method call, you might have to execute the application hundreds of times (or more) to witness this problem because the scheduler might rarely pause a thread between the amount <= balance expression and the balance -= amount; expression statement—the code executes rapidly.

Consider the following scenario:

  • The Husband thread executes withdraw()’s amount <= balance expression, which returns true. The scheduler pauses the Husband thread and lets the Wife thread execute.
  • The Wife thread executes withdraw()’s amount <= balance expression, which returns true.
  • The Wife thread performs the withdrawal. The scheduler pauses the Wife thread and lets the Husband thread execute.
  • The Husband thread performs the withdrawal.

This problem can be corrected by synchronizing access to withdraw() so that only one thread at a time can execute inside this method. You synchronize access at the method level by adding reserved word synchronized to the method header prior to the method’s return type, for example, synchronized boolean withdraw(int amount).

As I demonstrate later, you can also synchronize access to a block of statements by specifying synchronized( object ) { /* synchronized statements */ }, where object is an arbitrary object reference. No thread can enter a synchronized method or block until execution leaves the method/block; this is known as mutual exclusion.

Synchronization is implemented in terms of monitors and locks. A monitor is a concurrency construct for controlling access to a critical section, a region of code that must execute atomically. It is identified at the source code level as a synchronized method or a synchronized block.

A lock is a token that a thread must acquire before a monitor allows that thread to execute inside a monitor’s critical section. The token is released automatically when the thread exits the monitor to give another thread an opportunity to acquire the token and enter the monitor.

Note   A thread that has acquired a lock doesn’t release this lock when it calls one of Thread’s sleep() methods.

A thread entering a synchronized instance method acquires the lock associated with the object on which the method is called. A thread entering a synchronized class method acquires the lock associated with the class’s java.lang.Class object. Finally, a thread entering a synchronized block acquires the lock associated with the block’s controlling object.

Tip   Thread declares a static boolean holdsLock(Object o) method that returns true when the calling thread holds the monitor lock on object o. You will find this method handy in assertion statements, such as assert Thread.holdsLock(o);.

The need for synchronization is often subtle. For example, Listing 8-10’s ID utility class declares a getNextID() method that returns a unique long-based ID, perhaps to be used when generating unique filenames. Although you might not think so, this method can cause data corruption and return duplicate values.

Listing 8-10.  A Utility Class for Returning Unique IDs

class ID
{
   private static long nextID = 0;
   static long getNextID()
   {
      return nextID++;
   }
}

There are two lack-of-synchronization problems with getNextID(). Because 32-bit virtual machine implementations require two steps to update a 64-bit long integer, adding 1 to nextID is not atomic: the scheduler could interrupt a thread that has only updated half of nextID, which corrupts the contents of this variable.

Note   Variables of type long and double are subject to corruption when being written to in an unsynchronized context on 32-bit virtual machines. This problem doesn’t occur with variables of type boolean, byte, char, float, int, or short; each type occupies 32 bits or less.

Assume that multiple threads call getNextID(). Because postincrement (++) reads and writes the nextID field in two steps, multiple threads might retrieve the same value. For example, thread A executes ++, reading nextID but not incrementing its value before being interrupted by the scheduler. Thread B now executes and reads the same value.

Both problems can be corrected by synchronizing access to nextID so that only one thread can execute this method’s code. All that is required is to add synchronized to the method header prior to the method’s return type, for example, static synchronized int getNextID().

Synchronization is also used to communicate between threads. For example, you might design your own mechanism for stopping a thread (because you cannot use Thread’s unsafe stop() methods for this task). Listing 8-11 shows how you might accomplish this task.

Listing 8-11.  Attempting to Stop a Thread

public class ThreadStopping
{
   public static void main(String[] args)
   {
      class StoppableThread extends Thread
      {
         private boolean stopped = false;

         @Override
         public void run()
         {
            while(!stopped)
              System.out.println("running");
         }

         void stopThread()
         {
            stopped = true;
         }
      }
      StoppableThread thd = new StoppableThread();
      thd.start();
      try
      {
         Thread.sleep(1000); // sleep for 1 second
      }
      catch (InterruptedException ie)
      {
      }
      thd.stopThread();
   }
}

Listing 8-11 introduces a main() method with a local class named StoppableThread that subclasses Thread. StoppableThread declares a stopped field initialized to false, a stopThread() method that sets this field to true, and a run() method whose infinite loop checks stopped on each loop iteration to see if its value has changed to true.

After instantiating StoppableThread, the default main thread starts the thread associated with this Thread object. It then sleeps for one second and calls StoppableThread’s stop() method before dying. When you run this application on a single-processor/single-core machine, you will probably observe the application stopping. You might not see this stoppage when the application runs on a multiprocessor machine or a uniprocessor machine with multiple cores. For performance reasons, each processor or core probably has its own cache (localized high-speed memory) with its own copy of stopped. When one thread modifies its copy of this field, the other thread’s copy of stopped isn’t changed.

Listing 8-12 refactors Listing 8-11 to guarantee that the application will run correctly on all kinds of machines.

Listing 8-12.  Guaranteed Stoppage on a Multiprocessor/Multicore Machine

public class ThreadStopping
{
   public static void main(String[] args)
   {
      class StoppableThread extends Thread
      {
         private boolean stopped = false;

         @Override
         public void run()
         {
            while(!isStopped())
              System.out.println("running");
         }

         synchronized void stopThread()
         {
            stopped = true;
         }

         private synchronized boolean isStopped()
         {
            return stopped;
         }
      }
      StoppableThread thd = new StoppableThread();
      thd.start();
      try
      {
         Thread.sleep(1000); // sleep for 1 second
      }
      catch (InterruptedException ie)
      {
      }
      thd.stopThread();
   }
}

Listing 8-12’s stopThread() and isStopped() methods are synchronized to support thread communication (between the default main thread that calls stopThread() and the started thread that executes inside run()). When a thread enters one of these methods, it’s guaranteed to access a single shared copy of the stopped field (not a cached copy).

Synchronization is necessary to support mutual exclusion or mutual exclusion combined with thread communication. However, there exists an alternative to synchronization when the only purpose is to communicate between threads. This alternative is reserved word volatile, which Listing 8-13 demonstrates.

Listing 8-13.  The volatile Alternative to Synchronization for Thread Communication

public class ThreadStopping
{
   public static void main(String[] args)
   {
      class StoppableThread extends Thread
      {
         private volatile boolean stopped = false;

         @Override
         public void run()
         {
            while(!stopped)
              System.out.println("running");
         }

         void stopThread()
         {
            stopped = true;
         }
      }
      StoppableThread thd = new StoppableThread();
      thd.start();
      try
      {
         Thread.sleep(1000); // sleep for 1 second
      }
      catch (InterruptedException ie)
      {
      }
      thd.stopThread();
   }
}

Listing 8-13 declares stopped to be volatile; threads that access this field will always access a single shared copy (not cached copies on multiprocessor/multicore machines). In addition to generating code that is less verbose, volatile might offer improved performance over synchronization.

When a field is declared volatile, it cannot also be declared final. If you’re depending on the semantics (meaning) of volatility, you still get those from a final field. For more information, check out Brian Goetz’s “Java Theory and Practice: Fixing the Java Memory Model, Part 2” article (www.ibm.com/developerworks/library/j-jtp03304/).

Caution   Use volatile only in a thread communication context. Also, you can only use this reserved word in the context of field declarations. Although you can declare double and long fields volatile, you should avoid doing so on 32-bit virtual machines because it takes two operations to access a double or long variable’s value, and mutual exclusion via synchronization is required to access their values safely.

java.lang.Object’s wait(), notify(), and notifyAll() methods support a form of thread communication where a thread voluntarily waits for some condition (a prerequisite for continued execution) to arise, at which time another thread notifies the waiting thread that it can continue. wait() causes its calling thread to wait on an object’s monitor, and notify() and notifyAll() wake up one or all threads waiting on the monitor.

Caution   Because the wait(), notify(), and notifyAll() methods depend on a lock, they cannot be called from outside of a synchronized method or synchronized block. If you fail to heed this warning, you will encounter a thrown instance of the java.lang.IllegalMonitorStateException class. Also, a thread that has acquired a lock releases this lock when it calls one of Object’s wait() methods.

A classic example of thread communication involving conditions is the relationship between a producer thread and a consumer thread. The producer thread produces data items to be consumed by the consumer thread. Each produced data item is stored in a shared variable.

Imagine that the threads are not communicating and are running at different speeds. The producer might produce a new data item and record it in the shared variable before the consumer retrieves the previous data item for processing. Also, the consumer might retrieve the contents of the shared variable before a new data item is produced.

To overcome those problems, the producer thread must wait until it is notified that the previously produced data item has been consumed, and the consumer thread must wait until it is notified that a new data item has been produced. Listing 8-14 shows you how to accomplish this task via wait() and notify().

Listing 8-14.  The Producer-Consumer Relationship

public class PC
{
   public static void main(String[] args)
   {
      Shared s = new Shared();
      new Producer(s).start();
      new Consumer(s).start();
   }
}

class Shared
{
   private char c = 'u0000';
   private boolean writeable = true;

   synchronized void setSharedChar(char c)
   {
      while (!writeable)
         try
         {
            wait();
         }
         catch (InterruptedException e) {}
      this.c = c;
      writeable = false;
      notify();
   }

   synchronized char getSharedChar()
   {
      while (writeable)
         try
         {
            wait();
         }
         catch (InterruptedException e) {}
      writeable = true;
      notify();
      return c;
   }
}

class Producer extends Thread
{
   private Shared s;

   Producer(Shared s)
   {
      this.s = s;
   }

   @Override
   public void run()
   {
      for (char ch = 'A'; ch <= 'Z'; ch++)
      {
         synchronized(s)
         {
            s.setSharedChar(ch);
            System.out.println(ch + " produced by producer.");
         }
      }
   }
}
class Consumer extends Thread
{
   private Shared s;

   Consumer(Shared s)
   {
      this.s = s;
   }

   @Override
   public void run()
   {
      char ch;
      do
      {
         synchronized(s)
         {
            ch = s.getSharedChar();
            System.out.println(ch + " consumed by consumer.");
         }
      }
      while (ch != 'Z'),
   }
}

This application creates a Shared object and two threads that get a copy of the object’s reference. The producer calls the object’s setSharedChar() method to save each of 26 uppercase letters; the consumer calls the object’s getSharedChar() method to acquire each letter.

The writeable instance field tracks two conditions: the producer waiting on the consumer to consume a data item and the consumer waiting on the producer to produce a new data item. It helps coordinate execution of the producer and consumer. The following scenario, where the consumer executes first, illustrates this coordination:

  1. The consumer executes s.getSharedChar() to retrieve a letter.
  2. Inside of that synchronized method, the consumer calls wait() because writeable contains true. The consumer now waits until it receives notification from the producer.
  3. The producer eventually executes s.setSharedChar(ch);.
  4. When the producer enters that synchronized method (which is possible because the consumer released the lock inside of the wait() method prior to waiting), the producer discovers writeable’s value to be true and doesn’t call wait().
  5. The producer saves the character, sets writeable to false (which will cause the producer to wait on the next setSharedChar() call when the consumer has not consumed the character by that time), and calls notify() to awaken the consumer (assuming the consumer is waiting).
  6. The producer exits setSharedChar(char c).
  7. The consumer wakes up (and reacquires the lock), sets writeable to true (which will cause the consumer to wait on the next getSharedChar() call when the producer has not produced a character by that time), notifies the producer to awaken that thread (assuming the producer is waiting), and returns the shared character.

Although the synchronization works correctly, you might observe output (on some platforms) that shows multiple producing messages before a consuming message. For example, you might see A produced by producer., followed by B produced by producer., followed by A consumed by consumer. at the beginning of the application’s output.

This strange output order is caused by the call to setSharedChar() followed by its companion System.out.println() method call not being atomic and by the call to getSharedChar() followed by its companion System.out.println() method call not being atomic. The output order is corrected by wrapping each of these method call pairs in a synchronized block that synchronizes on the Shared object referenced by s.

When you run this application, its output should always appear in the same alternating order as shown next (only the first few lines are shown for brevity):

A produced by producer.
A consumed by consumer.
B produced by producer.
B consumed by consumer.
C produced by producer.
C consumed by consumer.
D produced by producer.
D consumed by consumer.

Caution   Never call wait() outside of a loop. The loop tests the condition (!writeable or writeable in the previous example) before and after the wait() call. Testing the condition before calling wait() ensures liveness. If this test was not present, and if the condition held and notify() had been called prior to wait() being called, it is unlikely that the waiting thread would ever wake up. Retesting the condition after calling wait() ensures safety. If retesting didn’t occur, and if the condition didn’t hold after the thread had awakened from the wait() call (perhaps another thread called notify() accidentally when the condition didn’t hold), the thread would proceed to destroy the lock’s protected invariants.

Too much synchronization can be problematic. If you are not careful, you might encounter a situation where locks are acquired by multiple threads, neither thread holds its own lock but holds the lock needed by some other thread, and neither thread can enter and later exit its critical section to release its held lock because some other thread holds the lock to that critical section. Listing 8-15’s atypical example demonstrates this scenario, which is known as deadlock.

Listing 8-15.  A Pathological Case of Deadlock

public class DeadlockDemo
{
   private Object lock1 = new Object();
   private Object lock2 = new Object();

   public void instanceMethod1()
   {
      synchronized(lock1)
      {
         synchronized(lock2)
         {
            System.out.println("first thread in instanceMethod1");
            // critical section guarded first by
            // lock1 and then by lock2
         }
      }
   }

   public void instanceMethod2()
   {
      synchronized(lock2)
      {
         synchronized(lock1)
         {
            System.out.println("second thread in instanceMethod2");
            // critical section guarded first by
            // lock2 and then by lock1
         }
      }
   }

   public static void main(String[] args)
   {
      final DeadlockDemo dld = new DeadlockDemo();
      Runnable r1 = new Runnable()
                    {
                       @Override
                       public void run()
                       {
                          while(true)
                          {
                             dld.instanceMethod1();
                             try
                             {
                                Thread.sleep(50);
                             }
                             catch (InterruptedException ie)
                             {
                             }
                          }
                       }
                    };
      Thread thdA = new Thread(r1);
      Runnable r2 = new Runnable()
                    {
                       @Override
                       public void run()
                       {
                          while(true)
                          {
                             dld.instanceMethod2();
                             try
                             {
                                Thread.sleep(50);
                             }
                             catch (InterruptedException ie)
                             {
                             }
                          }
                        }
                    };
      Thread thdB = new Thread(r2);
      thdA.start();
      thdB.start();
   }
}

Listing 8-15’s thread A and thread B call instanceMethod1() and instanceMethod2(), respectively, at different times. Consider the following execution sequence:

  1. Thread A calls instanceMethod1(), obtains the lock assigned to the lock1-referenced object, and enters its outer critical section (but has not yet acquired the lock assigned to the lock2-referenced object).
  2. Thread B calls instanceMethod2(), obtains the lock assigned to the lock2-referenced object, and enters its outer critical section (but has not yet acquired the lock assigned to the lock1-referenced object).
  3. Thread A attempts to acquire the lock associated with lock2. The virtual machine forces the thread to wait outside of the inner critical section because thread B holds that lock.
  4. Thread B attempts to acquire the lock associated with lock1. The virtual machine forces the thread to wait outside of the inner critical section because thread A holds that lock.
  5. Neither thread can proceed because the other thread holds the needed lock. You have a deadlock situation and the program (at least in the context of the two threads) freezes up.

Although the previous example clearly identifies a deadlock state, it’s often not that easy to detect deadlock. For example, your code might contain the following circular relationship among various classes (in several source files):

  • Class A’s synchronized method calls class B’s synchronized method.
  • Class B’s synchronized method calls class C’s synchronized method.
  • Class C’s synchronized method calls class A’s synchronized method.

If thread A calls class A’s synchronized method and thread B calls class C’s synchronized method, thread B will block when it attempts to call class A’s synchronized method and thread A is still inside of that method. Thread A will continue to execute until it calls class C’s synchronized method and then block. Deadlock results.

Note   Neither the Java language nor the virtual machine provides a way to prevent deadlock, and so the burden falls on you. The simplest way to prevent deadlock from happening is to avoid having either a synchronized method or a synchronized block call another synchronized method/block. Although this advice prevents deadlock from happening, it is impractical because one of your synchronized methods/blocks might need to call a synchronized method in a Java API, and the advice is overkill because the synchronized method/block being called might not call any other synchronized method/block, so deadlock would not occur.

You will sometimes want to associate per-thread data (such as 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 ThreadLocal as a simple (and very handy) alternative.

Each instance of the ThreadLocal class 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() creates a new thread-local variable.
  • T get() returns 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() creates the calling thread’s storage slot and stores 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() removes the calling thread’s storage slot. If this method is followed by get() with no intervening set(), get() calls initialValue().
  • void set(T value) sets the value of the calling thread’s storage slot to value.

Listing 8-16 shows how to use ThreadLocal to associate different user IDs with two threads.

Listing 8-16.  Different User IDs for Different Threads

public class ThreadLocalDemo
{
   private static volatile ThreadLocal<String> userID =
      new ThreadLocal<String>();

   public static void main(String[] args)
   {
      Runnable r = new Runnable()
                   {
                      @Override
                      public void run()
                      {
                         String name = Thread.currentThread().getName();
                         if (name.equals("A"))
                            userID.set("foxtrot");
                         else
                            userID.set("charlie");
                         System.out.println(name + " " + userID.get());
                      }
                   };
      Thread thdA = new Thread(r);
      thdA.setName("A");
      Thread thdB = new Thread(r);
      thdB.setName("B");
      thdA.start();
      thdB.start();
   }
}

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

When you run this application, you will 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) calculates 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 8-17 shows how to use InheritableThreadLocal to pass a parent thread’s Integer object to a child thread.

Listing 8-17.  Passing an Object from Parent Thread to Child Thread

public class InheritableThreadLocalDemo
{
   private static volatile InheritableThreadLocal<Integer> intVal =
      new InheritableThreadLocal<Integer>();

   public static void main(String[] args)
   {
      Runnable rP = new Runnable()
                    {
                       @Override
                       public void run()
                       {
                          intVal.set(new Integer(10));
                          Runnable rC = new Runnable()
                                        {
                                           @Override
                                           public void run()
                                           {
                                              Thread thd;
                                              thd = Thread.currentThread();
                                              String name = thd.getName();
                                              System.out.println(name + " " +
                                                                 intVal.get());
                                           }
                                        };
                          Thread thdChild = new Thread(rC);
                          thdChild.setName("Child");
                          thdChild.start();
                       }
                    };
      new Thread(rP).start();
   }
}

After instantiating InheritableThreadLocal and assigning it to a volatile class field named intVal, the default main thread creates a parent thread, which stores an Integer object containing 10 in intVal. The parent thread creates a child thread, which accesses intVal and retrieves its parent thread’s Integer object.

When you run this application, you will observe the following output:

Child 10

Exploring System Capabilities

The java.lang package includes four system-oriented classes: System, Runtime, Process, and ProcessBuilder. These classes let you obtain information about the system on which the application is running (such as environment variable values) and perform various system tasks (such as execute another application). For brevity, in this section I introduce you to the first three classes only.

Note   ProcessBuilder is a convenient alternative to Runtime for creating application processes and managing their attributes. To learn more about this class, check out “Getting Started with Java’s ProcessBuilder: A Simple Utility Class to Interact with Linux from Java Program” (http://singztechmusings.wordpress.com/2011/06/21/getting-started-with-javas-processbuilder-a-sample-utility-class-to-interact-with-linux-from-java-program/).

System

System is a utility class that declares in, out, and err class fields, which refer to the current standard input, standard output, and standard error streams, respectively. The first field is of type java.io.InputStream, and the last two fields are of type java.io.PrintStream. (I will formally introduce these classes in Chapter 11.)

System also declares class methods that provide access to the current time (in milliseconds), system property values, environment variable values, and other kinds of system information. Furthermore, it declares class methods that support the system tasks of copying one array to another array, requesting garbage collection, and so on.

Table 8-2 describes some of System’s methods.

Table 8-2. System Methods

Method Description
void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) Copy the number of elements specified by length from the src array starting at zero-based offset srcPos into the dest array starting at zero-based offset destPos. This method throws java.lang.NullPointerException when src or dest is null, java.lang.IndexOutOfBoundsException when copying causes access to data outside array bounds, and java.lang.ArrayStoreException when an element in the src array could not be stored into the dest array because of a type mismatch.
long currentTimeMillis() Return the current system time in milliseconds since January 1, 1970 00:00:00 UTC (Coordinated Universal Time—see http://en.wikipedia.org/wiki/Coordinated_Universal_Time).
void gc() Inform the virtual machine that now would be a good time to run the garbage collector. This is only a hint; there is no guarantee that the garbage collector will run.
String getEnv(String name) Return the value of the environment variable identified by name.
String getProperty(String name) Return the value of the system property (platform-specific attribute, such as a version number) identified by name or return null when such a property doesn’t exist. Examples of system properties that are useful in an Android context include file.separator, java.class.path, java.home, java.io.tmpdir, java.library.path, line.separator, os.arch, os.name, path.separator, and user.dir.
void runFinalization() Inform the virtual machine that now would be a good time to perform any outstanding object finalizations. This is only a hint; there is no guarantee that outstanding object finalizations will be performed.
void setErr(PrintStream err) Set the standard error device to point to err.
void setIn(InputStream in) Set the standard input device to point to in.
void setOut(PrintStream out) Set the standard output device to point to out.

Note   System declares SecurityManager getSecurityManager() and void setSecurityManager(SecurityManager sm) methods that are not supported by Android. On an Android device, the former method always returns null, and the latter method always throws an instance of the java.lang.SecurityException class. Regarding the latter method, its documentation states that “security managers do not provide a secure environment for executing untrusted code and are unsupported on Android. Untrusted code cannot be safely isolated within a single virtual machine on Android.”

Listing 8-18 demonstrates the arraycopy(), currentTimeMillis(), and getProperty() methods.

Listing 8-18.  Experimenting with System Methods

public class SystemDemo
{
   public static void main(String[] args)
   {
      int[] grades = { 86, 92, 78, 65, 52, 43, 72, 98, 81 };
      int[] gradesBackup = new int[grades.length];
      System.arraycopy(grades, 0, gradesBackup, 0, grades.length);
      for (int i = 0; i < gradesBackup.length; i++)
         System.out.println(gradesBackup[i]);
      System.out.println("Current time: " + System.currentTimeMillis());
      String[] propNames =
      {
         "file.separator",
         "java.class.path",
         "java.home",
         "java.io.tmpdir",
         "java.library.path",
         "line.separator",
         "os.arch",
         "os.name",
         "path.separator",
         "user.dir"
      };
      for (int i = 0; i < propNames.length; i++)
         System.out.println(propNames[i] + ": " +
                            System.getProperty(propNames[i]));
   }
}

Listing 8-18’s main() method begins by demonstrating arraycopy(). It uses this method to copy the contents of a grades array to a gradesBackup array.

Tip   The arraycopy() method is the fastest portable way to copy one array to another. Also, when you write a class whose methods return a reference to an internal array, you should use arraycopy() to create a copy of the array and then return the copy’s reference. That way, you prevent clients from directly manipulating (and possibly screwing up) the internal array.

main() next calls currentTimeMillis() to return the current time as a milliseconds value. Because this value is not human readable, you might want to use the java.util.Date class (discussed in Chapter 10). The Date() constructor calls currentTimeMillis() and its toString() method converts this value to a readable date and time.

main() concludes by demonstrating getProperty() in a for loop. This loop iterates over all of Table 8-2’s property names, outputting each name and value.

Compile Listing 8-18: javac SystemDemo.java. Then execute the following command line:

java SystemDemo

When I run this application on my platform, it generates the following output:

86
92
78
65
52
43
72
98
81
Current time: 1353115138889
file.separator:
java.class.path: .;C:Program Files (x86)QuickTimeQTSystemQTJava.zip
java.home: C:Program FilesJavajre7
java.io.tmpdir: C:UsersOwnerAppDataLocalTemp
java.library.path: C:Windowssystem32;C:WindowsSunJavain;C:Windowssystem32;C:Windows;c:Program Files (x86)AMD APPinx86_64;c:Program Files (x86)AMD APPinx86;C:Program FilesCommon FilesMicrosoft SharedWindows Live;C:Program Files (x86)Common FilesMicrosoft SharedWindows Live;C:Windowssystem32;C:Windows;C:WindowsSystem32Wbem;C:WindowsSystem32WindowsPowerShellv1.0;C:Program Files (x86)ATI TechnologiesATI.ACECore-Static;C:Program Files (x86)Windows LiveShared;C:Program Filesjavajdk1.7.0_06in;C:Program Files (x86)BorlandBCC55in;C:android;C:android ools;C:androidplatform-tools;C:Program Files (x86)apache-ant-1.8.2in;C:Program Files (x86)QuickTimeQTSystem;.
line.separator:

os.arch: amd64
os.name: Windows 7
path.separator: ;
user.dir: C:prjdevljfad2ch08codeSystemDemo

Note   line.separator stores the actual line separator character/characters, not its/their representation (such as ), which is why a blank line appears after line.separator :.

When you invoke System.in.read(), the input is originating from the source identified by the InputStream instance assigned to in. Similarly, when you invoke System.out.print() or System.err.println(), the output is being sent to the destination identified by the PrintStream instance assigned to out or err, respectively.

Tip   On an Android device, you can view content sent to standard output and standard error by first executing adb logcat at the command line. adb is one of the tools included in the Android SDK.

Java initializes in to refer to the keyboard or a file when the standard input device is redirected to the file. Similarly, Java initializes out/err to refer to the screen or a file when the standard output/error device is redirected to the file. You can specify the input source, output destination, and error destination by calling setIn(), setOut(), and setErr()—see Listing 8-19.

Listing 8-19.  Programmatically Specifying the Standard Input Device Source and Standard Output/Error Device Destinations

import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;

public class RedirectIO
{
   public static void main(String[] args) throws IOException
   {
      if (args.length != 3)
      {
         System.err.println("usage: java RedirectIO stdinfile stdoutfile stderrfile");
         return;
      }

      System.setIn(new FileInputStream(args[0]));
      System.setOut(new PrintStream(args[1]));
      System.setErr(new PrintStream(args[2]));

      int ch;
      while ((ch = System.in.read()) != -1)
         System.out.print((char) ch);

      System.err.println("Redirected error output");
   }
}

Listing 8-19 presents a RedirectIO application that lets you specify (via command-line arguments) the name of a file from which System.in.read() obtains its content as well as the names of files to which System.out.print() and System.err.println() send their content. It then proceeds to copy standard input to standard output and then demonstrates outputting content to standard error.

Note   FileInputStream provides access to the input sequence of bytes that is stored in the file identified, in this example, by args[0]. Similarly, the PrintStream provides access to the files identified by args[1] and args[2], which will store the output and error sequences of bytes.

Compile Listing 8-19: javac RedirectIO.java. Then execute the following command line:

java RedirectIO RedirectIO.java out.txt err.txt

This command line produces no visual output on the screen. Instead, it copies the contents of RedirectIO.java to out.txt. It also stores Redirected error output in err.txt.

Runtime and Process

Runtime provides Java applications with access to the environment in which they are running. An instance of this class is obtained by invoking its Runtime getRuntime() class method.

Note   There is only one instance of the Runtime class.

Runtime declares several methods that are also declared in System. For example, Runtime declares a void gc() method. Behind the scenes, System defers to its Runtime counterpart by first obtaining the Runtime instance and then invoking this method via that instance. For example, System’s static void gc() method executes Runtime.getRuntime().gc() ;.

Runtime also declares methods with no System counterparts. The following list describes a few of these methods:

  • int availableProcessors() returns the number of processors that are available to the virtual machine. The minimum value returned by this method is 1.
  • long freeMemory() returns the amount of free memory (measured in bytes) that the virtual machine makes available to the application.
  • long maxMemory() returns the maximum amount of memory (measured in bytes) that the virtual machine may use (or Long.MAX_VALUE when there is no limit).
  • long totalMemory() returns the total amount of memory (measured in bytes) that is available to the virtual machine. This amount may vary over time depending on the environment that is hosting the virtual machine.

Listing 8-20 demonstrates these methods.

Listing 8-20.  Experimenting with Runtime Methods

public class RuntimeDemo
{
   public static void main(String[] args)
   {
      Runtime rt = Runtime.getRuntime();
      System.out.println("Available processors: " + rt.availableProcessors());
      System.out.println("Free memory: "+ rt.freeMemory());
      System.out.println("Maximum memory: " + rt.maxMemory());
      System.out.println("Total memory: " + rt.totalMemory());
   }
}

Compile Listing 8-20: javac RuntimeDemo.java. Then execute the following command line:

java RuntimeDemo

When I run this application on my platform, I observe the following results:

Available processors: 2
Free memory: 123997936
Maximum memory: 1849229312
Total memory: 124649472

Some of Runtime’s methods are dedicated to executing other applications. For example, Process exec(String program) executes the program named program in a separate native process. The new process inherits the environment of the method’s caller, and a Process object is returned to allow communication with the new process. IOException is thrown when an I/O error occurs.

Tip   ProcessBuilder is a convenient alternative for configuring process attributes and running a process. For example, Process p = new ProcessBuilder("myCommand", "myArg").start();.

Table 8-3 describes Process methods.

Table 8-3. Process Methods

Method Description
void destroy() Terminate the calling process and close any associated streams.
int exitValue() Return the exit value of the native process represented by this Process object (the new process). IllegalThreadStateException is thrown when the native process has not yet terminated.
InputStream getErrorStream() Return an input stream that is connected to the standard error stream of the native process represented by this Process object. The stream obtains data piped from the error output of the process represented by this Process object.
InputStream getInputStream() Return an input stream that is connected to the standard output stream of the native process represented by this Process object. The stream obtains data piped from the standard output of the process represented by this Process object.
OutputStream getOutputStream() Return an output stream that is connected to the standard input stream of the native process represented by this Process object. Output to the stream is piped into the standard input of the process represented by this Process object.
int waitFor() Cause the calling thread to wait for the native process associated with this Process object to terminate. The process’s exit value is returned. By convention, 0 indicates normal termination. This method throws InterruptedException when the current thread is interrupted by another thread while it is waiting.

Listing 8-21 demonstrates exec(String program) and three of Process’s methods.

Listing 8-21.  Executing Another Application and Displaying Its Standard Output/Error Content

import java.io.InputStream;
import java.io.IOException;

public class Exec
{
   public static void main(String[] args)
   {
      if (args.length != 1)
      {
         System.err.println("usage: java Exec program");
         return;
      }
      try
      {
         Process p = Runtime.getRuntime().exec(args[0]);
         // Obtaining process standard output.
         InputStream is = p.getInputStream();
         int _byte;
         while ((_byte = is.read()) != -1)
            System.out.print((char) _byte);
         // Obtaining process standard error.
         is = p.getErrorStream();
         while ((_byte = is.read()) != -1)
            System.out.print((char) _byte);
         System.out.println("Exit status: " + p.waitFor());
      }
      catch (InterruptedException ie)
      {
         assert false; // should never happen
      }
      catch (IOException ioe)
      {
         System.err.println("I/O error: " + ioe.getMessage());
      }
   }
}

After verifying that exactly one command-line argument has been specified, Listing 8-21’s main() method attempts to run the application identified by this argument. IOException is thrown when the application cannot be located or when some other I/O error occurs.

Assuming that everything is fine, getInputStream() is called to obtain a reference to an input stream that is used to input the bytes that the newly invoked application writes to its standard output stream, if any. These bytes are subsequently output.

Next, main() calls getErrorStream() to obtain a reference to an input stream that is used to input the bytes that the newly invoked application writes to its standard error stream, if any. These bytes are subsequently output.

Note   To guard against confusion, remember that Process’s getInputStream() method is used to read bytes that the new process writes to its output stream, whereas Process’s getErrorStream() method is used to read bytes that the new process writes to its error stream.

Finally, main() calls waitFor() to block until the new process exits. If the new process is a GUI-based application, this method will not return until you explicitly terminate the new process. For simple command-line-based applications, Exec should terminate immediately.

Compile Listing 8-21: javac Exec.java. Then execute a command line that identifies an application, such as the java application launcher:

java Exec java

You should observe java’s usage information followed by the following line:

Exit status: 1

Caution   Because some native platforms provide limited buffer size for standard input and output streams, failure to promptly write the new process’s input stream or read its output stream may cause the new process to block or even deadlock.

EXERCISES

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

  1. What is a primitive type wrapper class?
  2. Identify Java’s primitive type wrapper classes.
  3. Why does Java provide primitive type wrapper classes?
  4. True or false: Byte is the smallest of the primitive type wrapper classes.
  5. Why should you use Character class methods instead of expressions such as ch >= '0' && ch <= '9' to determine whether or not a character is a digit, a letter, and so on?
  6. How do you determine whether or not double variable d contains +infinity or -infinity?
  7. Identify the class that is the superclass of Byte, Character, and the other primitive type wrapper classes.
  8. Define thread.
  9. What is the purpose of the Runnable interface?
  10. What is the purpose of the Thread class?
  11. True or false: A Thread object associates with multiple threads.
  12. Define race condition.
  13. What is thread synchronization?
  14. How is synchronization implemented?
  15. How does synchronization work?
  16. True or false: Variables of type long or double are not atomic on 32-bit virtual machines.
  17. What is the purpose of reserved word volatile?
  18. True or false: Object’s wait() methods can be called from outside of a synchronized method or block.
  19. Define deadlock.
  20. What is the purpose of the ThreadLocal class?
  21. How does InheritableThreadLocal differ from ThreadLocal?
  22. Identify the four java.lang package system classes discussed earlier in this chapter.
  23. What system method do you invoke to copy an array to another array?
  24. What does the exec(String program) method accomplish?
  25. What does Process’s getInputStream() method accomplish?
  26. Create a MultiPrint application that takes two arguments: text and an integer value that represents a count. This application should print count copies of the text, one copy per line.
  27. Modify Listing 8-4’s CountingThreads application by marking the two started threads as daemon threads. What happens when you run the resulting application?
  28. Modify Listing 8-4’s CountingThreads application by adding logic to stop both counting threads when the user presses the Return/Enter key. The default main thread of the new StopCountingThreads application should call System.in.read() before terminating and assign true to a variable named stopped after this method call returns. At each loop iteration start, each counting thread should test this variable to see if it contains true and only continue the loop when the variable contains false.
  29. Create an EVDump application that dumps all environment variables (not system properties) to the standard output.

Summary

The java.lang package includes Boolean, Byte, Character, Double, Float, Integer, Long, and Short. These classes are known as primitive type wrapper classes because their instances wrap themselves around values of primitive types.

Java provides these eight primitive type wrapper classes so that primitive-type values can be stored in collections, such as lists, sets, and maps. Furthermore, these classes provide a good place to associate useful constants and class methods with the primitive types.

Applications execute via threads, which are independent paths of execution through an application’s code. The virtual machine gives each thread its own method-call stack to prevent threads from interfering with each other.

Java supports threads via its Threads API. This API consists of one interface (Runnable) and four classes (Thread, ThreadGroup, ThreadLocal, and InheritableThreadLocal) in the java.lang package. ThreadGroup is not as useful as these other types.

Throughout its execution, each thread is isolated from other threads because it has been given its own method-call stack. However, threads can still interfere with each other when they access and manipulate shared data. This interference can corrupt the shared data, causing an application to fail.

Corruption can be avoided by using thread synchronization so that only one thread at a time can execute inside a critical section, a region of code that must execute atomically. It is identified at the source code level as a synchronized method or a synchronized block.

You synchronize access at the method level by adding reserved word synchronized to the method header prior to the method’s return type. You can also synchronize access to a block of statements by specifying synchronized( object ) { /* synchronized statements */ }.

Synchronization is implemented in terms of monitors and locks. A monitor is a concurrency construct for controlling access to a critical section. A lock is a token that a thread must acquire before a monitor allows that thread to execute inside a monitor’s critical section.

Synchronization is necessary to support mutual exclusion or mutual exclusion combined with thread communication. However, there exists an alternative to synchronization when the only purpose is to communicate between threads. This alternative is reserved word volatile.

Object’s wait(), notify(), and notifyAll() methods support a form of thread communication where a thread voluntarily waits for some condition (a prerequisite for continued execution) to arise, at which time another thread notifies the waiting thread that it can continue. wait() causes its calling thread to wait on an object’s monitor, and notify() and notifyAll() wake up one or all threads waiting on the monitor.

Too much synchronization can be problematic. If you are not careful, you might encounter a situation where locks are acquired by multiple threads, neither thread holds its own lock but holds the lock needed by some other thread, and neither thread can enter and later exit its critical section to release its held lock because some other thread holds the lock to that critical section. This scenario is known as deadlock.

You will sometimes want to associate per-thread data 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. Java supplies the 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. 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. When a new thread is created, it gets a new storage slot containing initialValue()’s value. However, you can pass a value from a parent thread, a thread that creates another thread, to a child thread, the created thread, by working with the InheritableThreadLocal class.

The java.lang package includes four system-oriented classes: System, Runtime, Process, and ProcessBuilder. These classes let you obtain information about the system on which the application is running and perform various system tasks.

System is a utility class that declares in, out, and err class fields, which refer to the current standard input, standard output, and standard error streams, respectively. The first field is of type InputStream and the last two fields are of type PrintStream.

System also declares class methods that provide access to the current time (in milliseconds), system property values, environment variable values, and other kinds of system information. Furthermore, it declares class methods that support system tasks such as copying one array to another array.

Runtime provides Java applications with access to the environment in which they are running. An instance of this class is obtained by invoking its Runtime getRuntime() class method. You can then call various environment-access methods, including methods that are also declared in System.

Some of Runtime’s methods execute other applications. For example, Process exec(String program) executes program in a separate native process. The new process inherits the environment of the method’s caller; a Process object is returned to allow communication with the new process.

This chapter completes my tour of Java’s basic APIs. In Chapter 9 I begin to explore Java’s utility APIs by focusing on the Collections Framework and classic collections APIs.

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

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