The Java Memory Model for Programmers

Introduction

Discussions of multithreaded programming and the Java Memory Model often get bogged down in discussion of complex compiler optimizations. I don’t think it needs to get that complicated—if all you want to do is write correct multithreaded programs, there’s a pretty simple mental model you can follow.

How to Think about Memory

Each thread has its own view of memory such that two threads can disagree on the contents of the same field. Writes made to a field by one thread may bleed through and become visible to others, but they may also appear only transiently, in the wrong order (even before they are “supposed” to happen!), or not at all. This is permitted for performance reasons.

However, Java does provide a few specific ways for threads to reliably communicate writes to one another…

The Tools at Your Disposal

There are five ways to explicitly ensure that one thread’s writes up to a certain point will be visible (and in the right order!) to other threads. Each one uses a particular sort of language primitive as a rendezvous for a writing thread to “publish” its writes, and for other threads to “receive” them:

Primitive Writes up to and including… ...are made visible to…
Object the end of a synchronized block or method a thread entering a synchronized block or method for the same object.
Volatile field a write to a volatile field any thread reading that volatile field.
Thread a call to Thread.start the newly started thread.
Thread the final write made by a dying thread any thread which successfully calls Thread.join on that thread.
Final field the initialization of a final field (but only those writes affecting the field and any object it references) any thread, provided that the constructor of the object containing the field doesn’t write the value of this anywhere eventually visible to other threads

Note that for the rendezvous to happen, it must be the same thread, the same field, or the same monitor on both the “publishing” and “receiving” sides.

Really, at all levels of abstraction, all correct concurrent programs share a particular characteristic: threads do not communicate with each other at all except when they mutually agree to communicate at common rendezvous points. Any truly unilateral attempt at communication (like Thread.suspend) is inherently unsafe.

Applying Your Knowledge

Let’s say we’ve got a class like this:

class Counter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
    public synchronized int getCount() {
        return count;
    }
}

Making increment synchronized ensures not only that the increment of count (which involves a read and then a write of the modified value) isn’t disturbed, it also ensures that the modified value of count is “published” where other threads can potentially see it.

Likewise, making getCount synchronized ensures that the thread “receives” any “published” changes to the value of count when the method body is entered.

Both synchronized declarations are necessary for this code to work as expected. Of course, since getCount is simply reading a single int field (which is already an atomic operation), there is another way to approach this:

class Counter {
    private volatile int count = 0;
    public synchronized void increment() {
        count++;
    }
    public int getCount() {
        return count;
    }
}

This means that increment will “publish” twice: once when it writes to count, and once when it exits the synchronized method. This has no ill effects and only a very minor peformance penalty.

getCount only “receives” once: when it reads count. Since it is not synchronized by a monitor, concurrent calls for getCount do not have to wait for each other to complete. This can result in improved performance for readers.

Note that, had increment performed any writes to other fields after the write to count, those writes would not be safely visible to callers of getCount; the second “publication” which happens upon existing the synchronized method is via the object’s monitor, not the volatile field.

The Consequences

If you do not properly coordinate the “publication” and “reception” of writes between threads that share fields, then your program will fail transiently. It is more likely to do so under production load on high-end hardware than it is during testing on your development machine.

You don’t want that. Neither does your boss.

Further Reading

The JSR 133 FAQ goes into more detail about the memory model itself. Like the Memory Model document, it goes a little more into the effects of specific kinds of optimizations.

One Last Thing

If you ever do get dragged into a discussion of compiler and runtime optimizations, it’s important to remember several things:

  1. Disassembling .class files doesn’t tell you much about what will happen at runtime: javac performs very few optimizations, leaving most of them up to the interpreter and JIT compiler.
  2. While the interpreter and the JIT compiler are prohibited from performing certain optimizations like reordering dependent reads or writes, the CPU itself is not.
  3. A few CPU architectures can and will reorder dependent memory operations and get away with it—unless you are writing improperly synchronized programs.
  4. Still, today’s hardware is often fairly forgiving about multithreading. As we’ve run out of room to scale “up” and have to scale “out” instead, future hardware will have to sacrifice that forgiveness for increased concurrent performance. Unless you want to rewrite all your code in five years, do it right the first time.

One Other Thing

Try using the classes in java.util.concurrent and java.util.concurrent.atomic before rolling your own custom solutions using synchronzied and volatile. The java.util.concurrent classes are likely to be both correct and as fast as possible while still maintaining correctness (which is seldom easy to achieve). They’re built on top of the basic primitives themselves, so they have similar characteristics: for example, writing to a java.util.concurrent queue “publishes” to the queue’s readers.

hoodwink.d enhanced