Re: JavaMemoryModel: acquire & releases on volatile variables

From: Doug Lea (dl@altair.cs.oswego.edu)
Date: Wed Oct 27 1999 - 07:18:49 EDT


> Now there is no one right answer, because before Java, no one tried
> to define the semantics of volatile. And right now, nobody uses
> volatile much in Java.

Well, the current JLS ch17 account of volatile is among the least
ambiguous parts of the spec, and some people (including me) do have
code relying on its stated properties. (For example my work-stealing
dequeue code in my fork/join framework in util.concurrent.) However,
assuming I understand it correctly, all of this code would continue to
work under your model (see below).

> If you treat reads and writes of a volatile variable as get/set
> methods that synchronize on a hidden monitor, then reads and writes
> each perform both an acquire and a release. So my proposal is weaker
> than synchronizing on a hidden monitor.

Translating your model into barrier-placement issues, two read
barriers are needed on read of a volatile (one before, and one after),
and two write barriers for a write. So the number of barriers is the
same for volatile and lock. For example, on a machine that needed
explicit barriers for all of these, double-check code would look like
(where RB = read barrier, WB = write barrier):

class X {
  int a, b;
  volatile boolean initialized = false;

  void doubleCheckedInit() {
                              // RB
    if (!initialized) {
                              // RB

      synchronized(this) { // lock acquire / RB
                              // RB
        if (!initialized) {
                              // RB
          a = 17;
          b = 42;
                              // flush + WB
          initialized = true;
                              // WB
        } // lock release / WB
      }
    }
  }
}

Because uncontended lock entry and exit are beginning to cost only a
few percent more than barriers (for example, when an acquire or
release is basically just a CAS instruction, that also acts as
barrier), people would normally be just as well off doing fully
synchronized checks on nonvolatiles here:

class X {
  int a, b;
  boolean initialized = false;

  synchronized void synchedInit() { // lock acquire / RB
    if (!initialized) {
      a = 17;
      b = 42;
      initialized = true;
    }
  } // lock release / WB
}

There are still only two barriers when initialized==true, but
now also only two when false.

In any case, I think the choices in specification amount to:

                      read write
1. single RB before WB after
2. monitor-style RB before, WB after RB before, WB after
3. double RB before, RB after WB before, WB after

The current spec seems to allow choice (1).

Choice (2) simplifies the rules (i.e., volatiles are just like
monitors).

Choice (3) might make more otherwise questionable existing code "work"
by adding volatile qualifiers to fields than would (2).

Note that in both (2) and (3) "extra" barriers can often be optimized
away, in both cases reducing to (1) (or even, rarely, no barriers at
all). Choice (2) seems easier to optimize in this way.

Also, as mentioned above, all existing code relying only on current
volatile spec (i.e., choice (1)) would continue to work with (2) or
(3), although possibly with a greater performance penalty than people
anticipated.

-- 
Doug Lea, Computer Science Department, SUNY Oswego, Oswego, NY 13126 USA
dl@cs.oswego.edu 315-341-2688 FAX:315-341-5424 http://gee.cs.oswego.edu/  



This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:21 EDT