This article is based on The Well-Grounded Java Developer, to be published Summer-2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code 'java40beat' and get 40% discount on eBooks and pBooks ]
Java Memory Model (JMM)
The Java Memory Model (JMM) is described in section 17.4 of the Java Language Specification (JLS). This is quite a formal part of the spec and describes the JMM in terms of synchronization actions and the mathematical construct known as a partial order. This is great from the point of view of language theorists and implementers of the Java spec (in other words, compiler and VM makers) but not as good for application developers who need to understand the details of how their multithreaded code will execute.
Rather than repeat the formal details, we’ll list the most important rules in terms of a couple of basic concepts—the synchronizes-with and happens-before relationships between blocks of code. (If you’ve studied formal approaches to OO programming you may have heard the expressions has-a and is-a to describe the building blocks of object orientation. Happens-before and synchronizes-with are similar conceptual building blocks for understanding Java concurrency.)
- HAPPENS-BEFORE This relationship indicates that one block of code fully completes before the other can start.
- SYNCHRONIZES-WITH This means that an action will synchronize its view of an object with main memory before continuing.
In figure 1, we can see an example of a volatile write, which synchronizes-with a later read access (for the println).
- An Unlock operation on a monitor synchronizes-with later lock operations.
- A write to a volatile variable synchronizes-with later reads of the variable.
- If an action A synchronizes-with action B, then A happens-before B.
- If A comes before B in program order within a thread, then A happens-before B.
The general statement of the first two rules is that “releases happen before acquires;” in other words, the locks that a thread holds when writing are released before the locks can be acquired by other operations (including reads). There are some other additional rules, which are really about sensible behavior:
- The completion of a constructor happens-before the finalizer for that object starts to run. (An object has to be fully constructed before it can be finalized.)
- An action which starts a Thread synchronizes-with the first action of the new Thread.
- Thread.join() synchronizes-with the last (and all other) actions in the thread being joined.
- (Transitivity) If X happens-before Y, and Y happens-before Z, then X happens-before Z.
These simple rules actually define the whole of the platform’s view of how memory and synchronization works, as you can see in figure 2.
In practice, these are the minimum guarantees made by the JMM. Real JVMs, however, may behave much better in practice than these guarantees suggest. This can be quite a pitfall for the developer, however, because it is easy for the false sense of safety given by the behavior of a particular JVM to turn out to be just a quirk, which is hiding an underlying concurrency bug.
We showed where Java’s conceptions of locks and synchronization come from as we explored the often-misunderstood details of the Java Memory Model (JMM). Many Java programmers are aware of the JMM and have been coding to their own understanding of it, without ever being formally introduced to it, so this new understanding builds upon that informal awareness and places it onto firm foundations.