CSC/ECE 506 Spring 2011/ch10a dc
Chapter 10a Supplement Java Memory Model
Introduction
This chapter supplement discusses the Java Memory Model (JMM). First of all, we will cover the motivation for a memory model for Java. Secondly, the main aspects of JMM including happens-before relationships and final and volatile variables. And lastly, we'll mention double-checking.
Motivation
With parallel computing becoming more popular, programming languages face many challenges including:
- multiple layers of cache across different processors (this introduces problems with cache consistancy)
- data races (that is, when two threads try to access the data at the same time when it is being altered by another thread)
- synchronization directives. Note: these may be different depending on whether you have a "strong" or "weak" memory model (see chapter ?? of the Solihin ___ textbook)
- compiler reordering of instructions
Programmers need to know how these issues effect program execution so that they can write good software for parallel systems. For example, C/C++ has the pthreads add-on to handle parallel programing. However, Java supports parallel programing by default. The JMM describes what is legal behavior for variables and memory accesses in programs with multiple threads.
Main Aspects
One of the most important parts of the Java Memory Model is the concept of "happens-before." Two statements have a happens-before relationship if one must come before the other.
Happens-before Illustration
For example:
// Thread 1: int xIsWritten = 0; x = 1; xIsWritten = 1;
// Thread 2:
while(xIsWritten) {};
In this example it is important that the statement x = 1 is completed before xIsWritten = 1, and that this order is seen by both threads. In other words, x = 1 and xIsWritten need to have a happens-before relationship.
Happens-before Rules
Brian Goetz in Java Concurrency in Practice explains that happens-before relationships require the following rules:
- Program order rule
- Monitor lock rule
- Volatile variable rule
- Thread start rule
- Thread termination rule
- Interruption rule
- Finalizer rule
- Transitivity
Let's unpack this list. First of all, program order simply means that order that statements are written in should be the order that every processor sees them executed as, if they have a happens-after relationship. Secondly, with a monitor lock, the monitor is a "key" that a thread must get before it can access a synchronized block of code, and only one thread can have the "key" at any one time. This means that only one thread at a time is working the block of code. The monitor lock rule insures that ever lock and unlock is done in order. The volatile variable rule says that a volatile variable is like a monitor locked variable. The two thread rules insures that the thread doesn't start executing before the call to Thread.start() has finished, and before it is terminated every thread sees the result of its instructions. Similar to the thread start rule, the interruption rule says that a thread must complete calling an interrupt before the interrupt is started in the other thread. Also similar, the finalizer rule means that the constructor is completed before the finalizer starts. Lastly, transitivity means that if A->B and B->C, then A->C.
These rules are the main framework for the Java programming model.
Secondary Aspects
Beyond the main principles of the Java Memory Model there are many details.
Double-checked Locking
One of the controversial issues that came up with parallel computing and the JMM was double-checked locking. People wanted to avoid time consuming synchronization so they implemented double-checked locking. Here is an example of double-checked locking from Jeremy Manson and Brien Goetz (2004)
// From Manson and Goetz (2004) // double-checked-locking - don't do this!
private static Something instance = null;
public Something getInstance() {
if (instance == null) { synchronized (this) { if (instance == null) instance = new Something(); } } return instance;
}
The problem with this code is that the reads may be reordered by the compiler (they are not happens-after statements). This means the code produces indeterminate results which could lead you to attempt to read from a null pointer while thinking that it had been initialized to new Something(). This can be avoided in the new version of the JMM by declaring the instance volatile due to the volatile rule.