12

Both C# and Java define that
* volatile reads have acquire semantics
* volatile writes have release semantics

My questions are:

  1. Is this the only correct way to define volatile.
  2. If not, will things be awfully different if the semantics were reversed, that is
    • volatile reads have release semantics
    • volatile writes have acquire semantics
yash
  • 187
  • 7
  • 3
    Where does Java define this? I have never heard of it before. – Peter Lawrey Jul 05 '12 at 22:17
  • 9
    For languages that are defined largely in terms of a _virtual machine_, something as specific as _CPU cache_ would feel out of place. – sarnold Jul 05 '12 at 22:17
  • 1
    Interesting document: http://g.oswego.edu/dl/jmm/cookbook.html by Doug Lea (Java concurrent team) – assylias Jul 05 '12 at 22:20
  • 3
    As @sarnold hints at, you are conflating a semantic requirement with an implementation detail. It so happens that a CPU cache may need to be refreshed in order to satisfy a volatile read (though that isn't *necessarily* the case) but that's merely a detail of how the definition of a volatile read might be enforced. – dlev Jul 05 '12 at 22:21
  • 3
    The last architecture where the CPU cache mattered was the Alpha. On all modern, surviving architectures that use multiple CPUs/cores, the CPU caches are made coherent in hardware using some variant of [MESI](http://en.wikipedia.org/wiki/MESI_protocol). So refreshing or flushing CPU caches would just reduce performance and would have *no* utility as a synchronization mechanism. – David Schwartz Jul 05 '12 at 22:22
  • @David: That's 90% of the way towards a great answer. :) – sarnold Jul 05 '12 at 22:23
  • @BenVoigt: Itanium is hardware cache coherent. Assuming by "CPU cache" you mean L1 and L2 caches, then they are also irrelevant on Itanium. (If you mean prefetch and write posting buffers, then they matter, but they're not all that matters.) – David Schwartz Jul 05 '12 at 23:08
  • @David, no doubt about cache coherency and nowadays everything is *eventually* consistent but not at the *same time* consistent. But yeah issuing CFLUSH doesn't make sense unless you'd like to put the computer in standby. – bestsss Jul 06 '12 at 11:26

2 Answers2

13

The reasoning behind the volatile semantic is rooted in the Java Memory Model, which is specified in terms of actions:

  • reads and writes to variables
  • locks and unlocks of monitors
  • starting and joining with threads

The Java Memory Model defines a partial ordering called happens-before for the actions which can occur in a Java program. Normally there is no guarantee, that threads can see the results of each other actions.

Let's say you have two actions A and B. In order to guarantee, that a thread executing action B can see the results of action A, there must be a happens-before relationship between A and B. If not, the JVM is free to reorder them as it likes.

A program which is not correctly synchronized might have data races. A data race occurs, when a variable is read by > 1 threads and written by >= 1 thread(s), but the read and write actions are not ordered through the happens-before ordering.

Hence, a correctly synchronized program has no data races, and all actions within the program happen in a fixed order.

So actions are generally only partially ordered, but there is also a total order between:

  • lock acquisition and release
  • reads and writes to volatile variables

These actions are totally ordered.

This makes it sensible to describe happens-before in terms of "subsequent" lock acquisitions and reads of volatile variables.

Regarding your questions:

  1. With the happen-before relationship you have an alternative definition of volatile
  2. Reversing the order would not make sense to the definition above, especially since there is a total order involved.

happens-before

This illustrates the happens-before relation when two threads synchronize using a common lock. All the actions within thread A are ordered by the program order rule, as are the actions within thread B. Because A releases lock M and B subsequently acquires M, all the actions in A before releasing the lock are therefore ordered before the actions in B after acquiring the lock. When two threads synchronize on different locks, we can't say anything about the ordering of actions between themthere is no happens-before relation between the actions in the two threads.

Source: Java Concurrency in Practice

Konrad Reiche
  • 27,743
  • 15
  • 106
  • 143
5

The power of the acquire/release semantics isn't so much about how soon other threads see the newly written value of the volatile field itself, but rather in the way volatile operations establish a happens-before relation across different threads. If a thread A reads a volatile field and sees a value that was written to that field in another thread B then thread A is also guaranteed to see values written to other (not necessarily volatile) variables by thread B before the point where it did the volatile write. This looks like cache flushing but only from the point of view of a thread that read the volatile, other threads that don't touch the volatile field have no ordering guarantees with respect to B and might see some of its earlier non-volatile writes but not others if the compiler/JIT is so inclined.

Monitor acquires/releases are similarly characterised by their induced happens-before relation - actions by one thread before a release of a monitor are guaranteed to be visible after a subsequent acquire of the same monitor by another thread. Volatiles give you the same ordering guarantees as monitor synchronisation but without blocking.

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
  • this is a good explanation of volatile's acquire and release semantics. but why must thread A see all non-volatile writes of thread B too. – yash Jul 06 '12 at 00:09
  • 2
    [This article](http://www.ibm.com/developerworks/library/j-jtp03304/) provides a good explanation. Basically it makes volatiles much more useful, in that they actually work for the case of setting a volatile flag to signify that a particular action has been completed - another thread reading the flag can know that the action _is_ complete rather than possibly seeing some partially complete intermediate state. – Ian Roberts Jul 06 '12 at 07:49
  • Great answer; I'm slowly starting to learn more about memory models and this helped to solidify my understanding. – anton.burger Jan 31 '13 at 15:15