In this article(part1, part2) published on the developWorks, Brian Goetz talked about the problems with Java Memory Model and the solutions proposed in JSR 133. The article helped me better understand an MSDN article I bloged about before. In the 'Thin Event' section of the MSDN article, Joe Duffy used Thread.MemoryBarrier method as follows.
private int m_state; // 0 means unset, 1 means set.Before I came accross Goetz's article, I didn't quite understand why it's required, though Duffy mentioned that "a legal transformation" in CLR 2.0 memory model necessitates the call to Thread.MemoryBarrier method. Now, I know why Duffy didn't explain the requirement, for it takes another article to explain it clearly. Even though Goetz's article is on JVM, the concepts applies to CLR. To understand why its' required, please read the following summary.
private EventWaitHandle m_eventObj;
private const int s_spinCount = 4000;
public void Set() {
m_state = 1;
Thread.MemoryBarrier(); // required.
if (m_eventObj != null) m_eventObj.Set();
}
As you may have known, the sequence of compiled code that gets executed in the CPU may not be the same as the sequence of source code, because the compiler, runtime, processor or cache may move compiled code around for performance reason. The optimizations are quite prevalent in a uniprocessor system, but weird things can happen in a multprocessor system. Therefore, rules are needed to specify how a program access variables in memory to avoid the problem. A memory model is a collection of such rules. The Java Memory Model(JMM) is defined in Chapter 17 of the Java Language Specification. It defines the semantics of synchronized, final and volatile.
The synchronized keyword ensures that only one thread can enter the synchronized block protected by a given monitor. JMM also specifies memory visibility rules for code in the synchronized block. According to JMM, caches are flushed when exiting a synchronized block and invalidated when entering one, and the compiler does not move instructions from inside a synchronized block to outside.
However, the original JMM exposed 2 problems. The first problem was immutable objects as decorated by final keyward might not be immutable. For example, in Sun 1.4 JDK, there are 3 important final fields: a reference to a character arrray, a length, and an offset into the character. Take a look at the following snippet, where 2 string objects are constructed outside synchronization block. Under the original JMM, due to the way object initialization works in Java, for code using s2, it might see '/user' for one moment and then '/temp'. That is, a final object is not final at all.
String s1 = "/user/tmp";Another problem was associated with volatile fields. The original JMM required that 1) volatile reads and writes are to go directly to main memory, prohibiting caching values in registers and bypassing processor-specific caches. 2)the compiler or cache cannot reorder volatile reads and writes with each other. The problem came with what is not required in original JMM. the JMM did allow ordinary variable reads and writes to be reordered with respect to volatile reads and writes. In the following code, thread A and thread B are coordinated by initialized volatile variable, without using synchronized block. So, in the original JMM, the write to initialized variable in thread A is allowed to be reordered above the assignment to configOption variable. That makes the result of using configOptions in thread B unknown.
String s2 = s1.substring(4);
Map configOptions;the JSR 133 Expert Group decided that it it makes sense for volatile reads and writes not to be reorderable with any other memory operations. The new JMM defines an ordering call happens-before, which is a partial ordering of all actions within a program. Under the new JMM, when thread A writes to a volatile varialbe V and thread B reads from V, any variable values that were visible to A at the time that V was written are guaranteed now to be visible to B. Although the guarantee imposes higher performance penalty for accessing volatile fields, it solves the problem mentioned above.
char[] configText;
volatile boolean initialized = false;
...
//In thread A
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfigOptions(configText, configOptions);
initialized = true;
//In thread B
while(!initialized) sleep();
//use configOptions
As for the final object problem, JMM provides a new guarantee of initialization safety, that is, a reference to the object is not published before the constructor has completed. As long as an object is constructed in this manner, all threads will see the values for its final fields that were set in its constructor, regardless of whether or not synchronization is used to pass the reference from one thread to another. Further, writes that initialize final fields will not be reordered with other operations in the constructor of the final object.
Further reading: The Java Memory Model