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:
In this section I introduce you to each of these primitive type wrapper classes and a java.lang class named Number.
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:
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:
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:
Character declares several comparison and conversion class methods that address these concerns. These methods include the following:
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:
Float and Double also declare the following constructors for initializing their objects:
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:
Double declares several utility methods as well as doubleValue(). These methods include the following:
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:
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’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):
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
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 Byte—Number’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.
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.
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
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_PRIORITY—Thread.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:
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).
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:
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:
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:
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):
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:
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:
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
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 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:
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
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:
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.
3.128.78.30