6

From the book Effective Java:

While the volatile modifier performs no mutual exclusion, it guarantees that any thread that reads the field will see the most recently written value

SO and many other sources claim similar things.

Is this true?

I mean really true, not a close-enough model, or true only on x86, or only in Oracle JVMs, or some definition of "most recently written" that's not the standard English interpretation...

Other sources (SO example) have said that volatile in Java is like acquire/release semantics in C++. Which I think do not offer the guarantee from the quote.

I found that in the JLS 17.4.4 it says "A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order)." But I don't quite understand.

There are quite some sources for and against this, so I'm hoping the answer is able to convince that many of those (on either side) are indeed wrong - for example reference or spec, or counter-example code.

Mark
  • 61
  • 2
  • 2
    "But I don't quite understand." What don't you understand? The JLS is the most authoritative reference for Java. – Sweeper Mar 16 '21 at 10:45
  • @Sweeper Don't understand whether that sentence means that other threads will immediately see the value, 'synchronizes-with' and 'subsequent' are not clear enough to me – Mark Mar 16 '21 at 10:49
  • 2
    "Is this true?" Yes. A volatile write happens-before a read of the same variable. – Andy Turner Mar 16 '21 at 10:49
  • 4
    "There are quite some sources for and against this" Please provide the sources against this. – Andy Turner Mar 16 '21 at 10:55
  • *"Other sources (SO example) have said that volatile in Java is like acquire/release semantics in C++."* - Actually, that question **asks** it, and neither of the answers actually answer that. (The accepted answer effectively says *"Here is the C# spec - you work it out!"*.) That hardly counts as a legitimate source of information. – Stephen C Mar 16 '21 at 10:58
  • 1
    You need to read it like this. If a load synchronizes with a particular store (so sees its value), then there is a happens before relation. So certain assumptions can be made about loads/store before the store and after the load. It doesn't say anything about recency. – pveentjer Apr 01 '21 at 12:15
  • @pveentjer Sounds that's what.I got from your answer and it reasonable, thanks. But the answers seem to contradict each other so I'm hesitant to accept one. – Mark Apr 01 '21 at 12:24
  • I would suggest digging into the meaning of sequential consistency. And also check out the following: https://jepsen.io/consistency/models/sequential If real time order should be respected, one needs learnability. Also check the link I posted in my answer. – pveentjer Apr 01 '21 at 12:27
  • The answers don't need to contradict each other. On a practical level you could say that a read needs to see the most recent write because the only way to proof it didn't read the most recent value would be to hook up special measuring equipment. And the hardware will not postpone reads/writes indefinitely. Only on a theoretical level reads/writes are allowed to be skewed indefinitely. – pveentjer Apr 01 '21 at 12:29

3 Answers3

8

Is this true?

I mean really true, not a close-enough model, or true only on x86, or only in Oracle JVMs, or some definition of "most recently written" that's not the standard English interpretation...

Yes, at least in the sense that a correct implementation of Java gives you this guarantee.

Unless you are using some exotic, experimental Java compiler/JVM (*), you can essentially take this as true.

From JLS 17.4.5:

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.


(*) As Stephen C points out, such an exotic implementation that doesn't implement the memory model semantics described in the language spec can't usefully (or even legally) be described as "Java".

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • 3
    I would say that an exotic Java that didn't behave according to the revised memory model in Java 5 and later is "not Java". And Oracle's lawyers (who guard the use of the Java trademarks) would agree. If a language implementation doesn't pass the JTK compliance tests, you are not allowed to call it Java (™). – Stephen C Mar 16 '21 at 11:12
3

It is not true. JMM is based on sequential consistency and for sequential consistency real time ordering isn't guaranteed; for that you need linearizability. In other words, reads and writes can be skewed as long as the program order isn't violated (or as long is it can't be proven po was violated).

A read of volatile variable a, needs to see the most recent written value before it in the memory order. But that doesn't imply real time ordering.

Good read about the topic: https://concurrency-interest.altair.cs.oswego.narkive.com/G8KjyUtg/relativity-of-guarantees-provided-by-volatile.

I'll make it concrete:

Imagine there are 2 CPU's and (volatile) variable A with initial value 0. CPU1 does a store A=1 and CPU2 does a load of A. And both CPUs have the cacheline containing A in SHARED state.

The store is first speculatively executed and written to the store buffer; eventually the store commits and retires, but since the stored value is still in the store buffer; it isn't visible yet to the CPU2. Till so far it wasn't required for the cacheline to be in an EXCLUSIVE/MODIFIED state, so the cacheline on CPU2 still contains the old value and hence CPU2 can still read the old value.

So in the real time order, the write of A is ordered before the read of A=0, but in the synchronization order, the write of A=1 is ordered after the read of A=0.

Only when the store leaves the store buffer and wants to enter the L1 cache, the request for ownership (RFO) is send to all other CPU's which set the cacheline containing A to INVALID on CPU2 (RFO prefetching I'll leave out of the discussion). If CPU2 would now read A, it is guaranteed to see A=1 (the request will block till CPU1 has completed the store to the L1 cache).

On acknowledgement of the RFO the cacheline is set to MODIFIED on CPU1 and the store is written to the L1 cache.

So there is a period of time between when the store is executed/retired and when it is visible to another CPU. But the only way to determine this is if you would add special measuring equipment to the CPUs.

I believe a similar delaying effect can happen on the reading side with invalidation queues.

In practice this will not be an issue because store buffers have a limited capacity and need to be drained eventually (so a write can't be invisible indefinitely). So in day to day usage you could say that a volatile read, reads the most recent write.

A java volatile write/read provides release/acquire semantics, but keep in mind that the volatile write/read is stronger than release/acquire semantics. A volatile write/read is sequential consistent and release/acquire semantics isn't.

pveentjer
  • 10,545
  • 3
  • 23
  • 40
  • I think that book was only implying sequential consistency behavior, as denoted by that "any thread", but otherwise I agree with you : the quote is in general wrong. – Eugene Mar 16 '21 at 15:13
3

The quote per-se is correct in terms of what is tries to prove, but it is incorrect on a broader view.

It tries to make a distinction of sequential consistency and release/acquire semantics, at least in my understanding. The difference is rather "thin" between these two terms, but very important. I have tried to simplify the difference at the beginning of this answer or here.

The author is trying to say that volatile offers that sequential consistency, as implied by that:

"... it guarantees that any thread.."

If you look at the JLS, it has this sentence:

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

The tricky part there is that subsequent and it's meaning, and it has been discussed here. What is really wants to mean is "subsequent that observes that write". So happens-before is guaranteed when the reader observes the value that the writer has written.

This already implies that a write is not necessarily seen on the next read, and this can be the case where speculative execution is allowed. So in this regard, the quote is miss-leading.

The quote that you found:

A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order)

is a complicated to understand without a much broader context. In simple words, it established synchronizes-with order (and implicitly happens-before) between two threads, where volatile v variables is a shared variable. here is an answer where this has broader explanation and thus should make more sense.

Eugene
  • 117,005
  • 15
  • 201
  • 306