2

Coming from C/C++, I am a little confused about volatile object behavior in Java.

I understand that volatile in Java has two properties:

  1. Won't bring object into cache, always keep it in main memory.
  2. Guarantee "happen-before"

However I am not sure what happens if I make a new non-volatile reference to object. For example,

class Example {
   private volatile Book b = null;

   public init() { b = new Book(...); }

   public use() {
     Book local = b;
     local.read();
   }
}

AFAIK, volatile means the "book object" that b is referencing to should be in main memory. Compiler probably implement reference as pointer internally, so the b pointer probably sit in cache. And to my understanding, volatile is a qualifier for object, not for reference/pointer.

The question is: in the use method, the local reference is not volatile. Would this "local" reference bring the underlying Book object from main memory into cache, essentially making the object not "volatile"?

Oliver Young
  • 578
  • 1
  • 4
  • 12
  • 4
    volatile is about the reference, not the object. It guarantees that any thread reading the variable `b` after another thread has set the value of `b` will get the value assigned to `b` by the other thread, and not some cached value. – JB Nizet Jan 19 '18 at 08:06
  • Btw, my understanding/reading of the JLS is that it only really provides the happens-before. But once that's there, the JVM has to provide a total ordering of operations, which effectively means that there only way to make any progress is to read the book-cached value. In other words, your property 1 is not a first-order property, but rather a consequence of property 2. – yshavit Jan 19 '18 at 08:12
  • Related: https://stackoverflow.com/questions/17748078/simplest-and-understandable-example-of-volatile-keyword-in-java – Mark Rotteveel Jan 19 '18 at 08:12
  • @JBNizet You should consider posting that as an answer – Mark Rotteveel Jan 19 '18 at 08:13
  • In this case, it means `local` will always get fresh b. – xingbin Jan 19 '18 at 08:16

2 Answers2

4

There is no such thing as a “volatile object” nor “always keep it in main memory” guarantees.

All that volatile variables of a reference type guaranty, is that there will be a happens-before relationship between a write to that variable and a subsequent read of the same variable.

Since happens-before relationships are transitive, they work for your example code, i.e. for b = new Book(...) all modifications made to the Book instance are committed before the reference is written to b and hence for Book local = b; local.read(); the read() is guaranteed to see all these modification made by the other thread before writing the reference.

This does not imply that the Book instance’s memory was special. E.g. modifications made to the instance after the reference has been written to b may or may not be visible to other threads and other threads may perceive only some of them or see them as if being made in a different order.

So it doesn’t matter which way you get the reference to the object, all that matters is whether the changes are made before or after publishing a reference to that object through b. Likewise, it doesn’t matter how you perform the read access to the object, as long as you do it after having acquired the reference by reading b.

With local.read(); you are accessing the object via the local variable local and within read(), the same reference will be accessed though this, but all that matters is that you have acquired the reference by reading b before reading the object’s state.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I think that writing to a volatile and then reading it, is happens-before *consistency* IIRC from the JLS, as simply a volatile write is a happens-before, but without guarantees, unless a read is done on the same volatile that can observer the side-effect of the write – Eugene Jan 19 '18 at 11:46
  • meaning that the `local.read()` can observer the correctly written fields of `Book` only if it sees the non-null reference of `Book` (via the volatile read) – Eugene Jan 19 '18 at 11:49
  • @Eugene *happens-before* is a *relation* between two actions, so a single action can’t be a *hb*. Calling an instance method implies dereferencing, so if the reference is `null`, you get a `NullPointerException` and the method won’t be entered (so `this` is always guaranteed to be non-`null`). If `local` is always initialized with a value read from `b` the subsequently invoked `read()` method is guaranteed to consistently see values written before *some* publication of a reference, but if multiple threads executed `init()` concurrently, you don’t know which one… – Holger Jan 19 '18 at 12:03
  • my point was a bit different I think. Consider the case `if(x == 3) ... synchronized(this)/volatile write .... if(x == 3)...` the second `if(x == 3)` does not guarantee that `x==3`. I think this is what the JLS means by happens before consistency here if I understood it correctly. – Eugene Jan 19 '18 at 12:36
  • @Eugene sorry, but I’m not able to recognize what your point is. The JMM guarantees no “out-of-thin-air” values, hence, the value of `x` can only change, if there is a thread writing to it. Discussing whether this actually may happen for a particular program is only possible by considering all threads actually accessing `x`. – Holger Jan 19 '18 at 13:27
  • Thank you for the explanation! Where do you read about this? Could you point me to some authorized docs to read about volatile? I have had a hard time finding that. – Oliver Young Jan 19 '18 at 16:38
  • The [Java Memory Model](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4), being part of the language specification is the authoritative source, but for an easier read, “Java Concurrency in Practice”, ISBN:8601419048427 would be a good start. – Holger Jan 19 '18 at 16:41
  • @Holger In your answer `Modifications made to the instance after the reference has been written to b may or may not be visible to other threads` - Do you refer to concurrent threads modifying it? – Thiyagu Feb 27 '18 at 17:15
  • @user7 depends on what you mean with “concurrent threads”. The phrase “other threads” already implies the presence of more than one thread, in other words, concurrent access. Whether these other threads are also modifying the object or just reading, is irrelevant, if there is at least one modifying thread, all concurrent access requires using appropriate thread safe constructs. – Holger Feb 27 '18 at 17:23
  • @Holger So you mean the order in which these modifications are flushed to the memory cannot be guaranteed? – Thiyagu Feb 27 '18 at 17:37
  • @user7 there isn’t any guaranty that modifications are “flushed to the memory” at all. – Holger Feb 27 '18 at 17:43
  • @Holger I meant when there is a *subsequent read*, there is no guarantee on which update will be seen – Thiyagu Feb 27 '18 at 17:46
  • @user7 for a single value, you can say that there is no guaranty to see the subsequent update, or in the presence of multiple updates, which update it will see. If the modification is not as trivial as modifying a single atomic property, the reading threads may see inconsistent values, each being either at the published state or of an arbitrary subsequent update, not fitting together. Consider an operation as simple as `ArrayList.add`; it updates at least a count variable and an array element, but it may also replace the array reference when increasing the capacity, writing every element… – Holger Feb 27 '18 at 17:53
  • @Holger Thanks for the explanation. I think I was confusing volatile with atomicity. – Thiyagu Feb 27 '18 at 18:08
2

volatile is about the reference, not the object.

It guarantees that any thread reading the variable b after another thread has set the value of b will get the value assigned to b by the other thread, and not some cached value.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255