2

After reading this question and this (especially second answer) I am massively confused about volatile and its semantics with respect to memory barriers.

In the above examples, we write to a volatile variable, which causes an mfence, which in turn flushes all pending store buffers/load buffers to main cache, invalidating other cache lines.

However, the non-volatile fields could be optimized and be stored in registers for example? So how can we be sure that given a write to volatile variable ALL state changes prior to it will be visible? What if we cahnge 1000 things?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Bober02
  • 15,034
  • 31
  • 92
  • 178

4 Answers4

4

The guarantee that the JMM gives is - if Thread 1 writes a volatile variable and after that Thread 2 reads that same volatile variable, then Thread 2 is guaranteed to see all changes made by Thread 1 prior to writing the volatile variable (including changes made to non-volatile variables). This is a strong guarantee that exists and everyone agrees to.

However, the guarantee applies only to what Thread 2 sees. You may still have another thread, Thread 3, which may NOT see up-to-date values for the non-volatile fields set by Thread 1 (Thread 3 may have, and is allowed to, cache values for those non-volatile fields). Only after Thread 3 reads the same volatile is it guaranteed to see non-volatile writes from Thread 1

Ashutosh A
  • 975
  • 7
  • 10
2

In the above examples, we write to a volatile variable, which causes an mfence, which in turn flushes all pending store buffers/load buffers to main cache...

This is correct.

invalidating other cache lines.

This is not correct or at least is misleading. It is not the write memory-barrier which invalidates the other cache lines. It is the read memory-barrier running in the other processors which invalidates each processor's cache lines. Memory synchronization is cooperative action between the thread writing and the other threads reading from volatile variables.

The Java memory model actually guarantees that only a read of the same variable that was written to will the variable be guaranteed to be updated. The reality is that all memory cache lines are flushed upon a write memory barrier being crossed and all memory cache lines are invalidated when a read memory barrier is crossed – regardless of the variable being accessed.

However, the non-volatile fields could be optimized and be stored in registers for example? So how can we be sure that given a write to volatile variable ALL state changes prior to it will be visible? What if we change 1000 things?

According to this documentation (and others), memory barriers also cause the compiler to generate code that flushes registers as well. To quote:

... while with barrier() the compiler must discard the value of all memory locations that it has currently cached in any machine registers.

Gray
  • 115,027
  • 24
  • 293
  • 354
  • "It is the read memory-barrier running in the other processors which invalidates each processor's cache lines" - but read of volatile generates no fence on x86...? – Bober02 Aug 11 '17 at 15:15
  • Can you quote a source on that @Bober02? – Gray Aug 11 '17 at 15:35
  • Although that may be technically true @Bober02 , the devil is in the details so just saying that seems to say that volatile reads are "free" and they are not on x86. Regardless of what assembly code is emitted, the compiler or some other entity needs to implement the memory specifications. So when I use "memory barrier", I'm talking about the JMM, not what the hardware is doing. See: http://brooker.co.za/blog/2012/09/10/volatile.html – Gray Sep 07 '17 at 15:21
  • 1
    This is not entirely correct: "The Java memory model actually guarantees that only a read of the same variable that was written to will the variable be guaranteed to be updated." - although only a read of the *same* volatile variable that was written to, sets up a happens-before relationship between two threads, the effect is that *everything* (all memory writes to any field, no matter whether volatile or not) that happened before the *write* to the volatile variable in one thread, appears to have happened before the *read* of the volatile variable in the other thread. See JLS section 17.4.5 – Erwin Bolwidt Apr 19 '18 at 03:45
  • This is true in terms of the memory model @ErwinBolwidt but not true in terms of implementation of the model. Try to imagine how a JVM would implement the memory barriers if it had to pair them up somehow. – Gray Apr 23 '18 at 11:38
  • So do you suggest that JVM doesn't correctly implement JMM? @ErwinBolwidt is right and JMM implements it this way. – Oliv Feb 12 '19 at 08:52
  • No it implements the JMM correctly. I'm just saying that a byproduct of the of memory barriers flush all memory. Find any mention of specific memory addresses in Doug's documentation for example: http://gee.cs.oswego.edu/dl/jmm/cookbook.html. To quote from that link "On processors that guarantee to always preserve load ordering, the barriers amount to no-ops.". So if a processor preserves load ordering then the memory barriers can be a no-op. Here's more on x86's memory reordering restrictions: https://stackoverflow.com/a/50310563/179850 – Gray Feb 12 '19 at 20:55
  • @Gray The JMM is not (just) about current hardware architectures, it's about the *entire* virtual machine. For example, if the VM detects using escape analysis that an object never becomes accessible by another thread, it can ignore the `volatile` modifier on fields in the object. In that case it is very important that the JMM states what it states about accessing the *same* volatile field. (And, who knows what future hardware architectures hold in store.) – Erwin Bolwidt Feb 12 '19 at 23:23
  • Can I get a reference to your first point @ErwinBolwidt? I see docs on escape analysis in terms of memory allocation but not in terms of volatile flagging. I also see specific docs around how you can remove a variable access through optimization (including escape analysis) but you _can't_ remove the memory fence. – Gray Feb 13 '19 at 20:00
  • 1
    You can find a statement [in this link](https://wiki.openjdk.org/display/HotSpot/EscapeAnalysis#:~:text=After%20escape%20analysis,escaping%20objects.), for example: “*After escape analysis C2 eliminates scalar replaceable object allocations and associated locks. C2 also eliminates locks for all non globally escaping objects.*”. But even if locks are not eliminated, the optimizer may merge adjacent synchronized blocks when they use the same object, which has no effect on threads using the same object, as their execution is mutually exclusive, but removes the memory barriers between the blocks. – Holger Jun 08 '23 at 13:48
1

Volatile variables share the visibility features of synchronized, but none of the atomicity features. This means that threads will automatically see the most up-to-date value for volatile variables.

You can use volatile variables instead of locks only under a restricted set of circumstances. Both of the following criteria must be met for volatile variables to provide the desired thread-safety:

Writes to the variable do not depend on its current value.

The variable does not participate in invariant with other variables.

gati sahu
  • 2,576
  • 2
  • 10
  • 16
0

The compiler is responsible for ensuring the semantics of the memory model are preserved. In your example, the compiler would ensure that any cross-thread visible values are written to memory prior to the volatile write (on x86 a plain store is sufficient for this purpose).

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386