The Java language provides flexible, seemingly simple threading capabilities that make it easy to use multithreading in your applications. However, concurrent programming in Java applications is more complex than it seems: in Java programs, there are subtle (and perhaps not subtle) ways to create data contention (race) and concurrency problems. In this Java theory and Practice, Brian explores a common thread hazard: Allow this reference to escape during construction. This seemingly harmless approach can cause unpredictable and unintended results in Java programs.
It is extremely difficult to test and debug multithreaded programs, because the risk of concurrency is often not manifested in a consistent manner, and may not even be apparent at times. In terms of the nature of threading problems, most of these problems are unpredictable, even on certain platforms (such as single-processor systems), or below a certain load, and the problem may not occur at all. Since it is so difficult to test the correctness of multithreaded programs, and it is so time-consuming to find errors, it is particularly important to develop applications from the outset to keep in mind the security of threads. In this article, we'll look at a particular thread-safety issue--allowing the this reference to escape (what we call an escape reference problem) in the construction process--the problem is causing some unexpected results. Then, we give some guidelines for writing thread-safe constructors.
Follow the "Security constructs" technology
It is very difficult to analyze the procedure to find out the safety of the thread, which requires special experience. Fortunately (and perhaps surprisingly), creating a thread-safe class from the outset is not that difficult, although it requires a different kind of special technique: discipline. Most concurrency errors are caused by programmers attempting to violate the rules for convenience, improved performance, or just a momentary laziness. As with many other concurrency issues, when you write a constructor, you can avoid this escaping reference problem by following some simple rules.
A dangerous race state
Most of the concurrency risks are ultimately caused by some kind of data contention. Data contention or race status occurs when multiple threads or processes are reading and writing a shared data item, and the final result depends on the scheduling order of those threads. Listing 1 shows an example of a simple data contention in which a program can print 0 or 1, depending on the scheduling of threads.
Listing 1. Simple data contention
public class DataRace {
static int a = 0;
public static void main() {
new MyThread().start();
a = 1;
}
public static class MyThread extends Thread {
public void run() {
public void run() {
System.out.println(a);
}
}
}
The second thread can be dispatched immediately, printing A's initial value of 0. In another case, the second thread may not run immediately, resulting in a print value of 1. The output of this program depends on the JDK you are using, the scheduler for the underlying operating system, or the random timer widget. Running the program repeatedly will result in different results.
Visibility risk
In Listing 1, in addition to this obvious contention--whether the second thread starts before or after the first thread has set a to 1--there is actually another data contention. The second contention is a visibility contention: Two threads do not use synchronization, and synchronization guarantees the visibility of data changes between threads. Because there is no synchronization, if you run the second thread after the first thread has completed the assignment of a, the second thread might or may not immediately see the changes made by the first thread. The second thread may see that a is still 0, even if the first thread has assigned a value of 1 to a. This second kind of data contention (in the absence of proper synchronization, two threads are accessing the same variable) is a complex problem, but fortunately, using synchronization avoids this kind of data contention whenever you read a variable that another thread might have written, or if you write a variable that may be read by another thread next. Here, we do not want to explore this kind of data contention further, you can refer to the sidebar, "Using Java Memory Model synchronization", or see resources for more details on such complex issues.