15

Suppose I have following code

private volatile Service service;

public void setService(Service service) {
  this.service = service;
}

public void doWork() {
  service.doWork();
}

Modified field marked as volatile and its value do not depend on previous state. So, this is correct multithreaded code (don't bother about Service implementations for a minute).

As far as I know, reading volatile variable is like entering a lock, from perspective of memory visibility. It's because reading of normal variables can not be reordered with reading volatile variables.

Does this mean that following code is correct?

private volatile boolean serviceReady = false;
private Service service;

public void setService(Service service) {
  this.service = service;
  this.serviceReady = true;
}

public void doWork() {
  if ( serviceReady ) {
    service.doWork();
  }
}
Denis Bazhenov
  • 9,680
  • 8
  • 43
  • 65

4 Answers4

20

Yes, this code is 'correct' as it stands, from Java 1.5 onwards.

Atomicity is not a concern, with or without the volatile (writes to object references are atomic), so you can cross that off the concerns list either way -- the only open question is visibility of changes and the 'correctness' of the ordering.

Any write to a volatile variable sets up a 'happens-before' relationship (the key concept of the new Java Memory Model, as specified in JSR-133) with any subsequent reads of the same variable. This means that the reading thread must have visibility into everything visible to the writing thread: that is, it must see all variables with at least their 'current' values at the time of the write.

We can explain this in detail by looking at section 17.4.5 of the Java Language Specification, specifically the following key points:

  1. "If x and y are actions of the same thread and x comes before y in program order, then hb(x, y)" (that is, actions on the same thread cannot be reordered in a way to be inconsistent with program order)
  2. "A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field." (this is clarifying text, explaining that write-then-read of a volatile field is a synchronization point)
  3. "If hb(x, y) and hb(y, z), then hb(x, z)" (transitivity of happens-before)

So in your example:

  • the write to 'service' (a) happens-before the write to 'serviceReady' (b), due to rule 1
  • the write to 'serviceReady' (b) happens-before the read of same (c), due to rule 2
  • therefore, (a) happens-before (c) (3rd rule)

meaning that you are guaranteed that 'service' is set correctly, in this instance, once serviceReady is true.

You can see some good write-ups using almost exactly the same example, one at IBM DeveloperWorks -- see "New Guarantees for Volatile":

values that were visible to A at the time that V was written are guaranteed now to be visible to B.

and one at the JSR-133 FAQ, written by the authors of that JSR:

Thus, if the reader sees the value true for v, it is also guaranteed to see the write to 42 that happened before it. This would not have been true under the old memory model. If v were not volatile, then the compiler could reorder the writes in writer, and reader's read of x might see 0.

Cowan
  • 37,227
  • 11
  • 66
  • 65
  • 1
    That's why my eyes always cross with the JLS: it appeared to me that the happens-before relationship was not guaranteed across threads for the non-volatile. Thanks for the JSR-133 reference, and my answer is gone. – kdgregory Aug 31 '09 at 11:34
  • If serviceReady is not volatile, it seems the conclusion of "the write to 'service' (a) happens-before the write to 'serviceReady' (b)" due to rule 1. But actually, it is not. So I feel there is something missing in the 3 rules. What do you think? – zwy Mar 06 '18 at 01:51
2

AFAIK this is correct code.

@CPerkins: making the only the setService method synchronized won't work as you also have to synchronize on reads.

However, one variable would be enough in this case. Why do you need the extra boolean field. E.g.

private volatile Service service;

public void setService(Service service) {
  this.service = service;
}

public void doWork() {
  if ( service != null ) {
    service.doWork();
  }
}

given that no one ever calls setService to null. So you should probably a null-check:

private volatile Service service;

public void setService(Service service) {
  if (service == null) throw NullPointerException();
  this.service = service;
}

public void doWork() {
  if ( service != null ) {
    service.doWork();
  }
}
Kutzi
  • 1,295
  • 1
  • 15
  • 19
  • Yes, quite right, Kutzi. I wrote in haste, mostly responding to the question about volatile... but I still ask: why not simply synchronize? – CPerkins Aug 29 '09 at 16:12
  • Yeah, I agree with you about design. But code is invented, not real. Question is about volatile semantic. – Denis Bazhenov Aug 29 '09 at 23:57
  • @dotsid: If you don't have an example which actually needs the extra volatile field it's difficult to see what you want tom achieve – Kutzi Aug 30 '09 at 22:29
  • CPerkins: there's one reason to use volatile over synchronized: it's usually much faster – Kutzi Aug 30 '09 at 22:30
1

You're right about the effects of volatile, so this should be correct, but I'm confused about your design. I don't understand why you don't just synchronize setService - it probably wouldn't be called often. If it is called more than once, the "if (serviceReady)" part is moot, since it'll still be true, but that's fine, since the replacement is atomic, if I understand correctly.

I gather that service.doWork() is thread-safe, yes?

CPerkins
  • 8,968
  • 3
  • 34
  • 47
  • This is not real code. In real system I would not do it that way, of course :) I am invented this code only as a teaching example. – Denis Bazhenov Aug 29 '09 at 23:54
0

In theory, it should never work. You want to insure memory consistency for two variables, and you want to rely on a volatile read on the first one. The volatile read only guarantees that a reading thread sees the most recent value of the variable. So it's definitely not as strong as entering a locked ( synchronized ) section.

In practice, it might work, depending on the implementation of volatile by the JVM you're using. If volatile reads are implemented by flushing all CPU caches, it should work. But I'm ready to bet that it will not happen. Can I force cache coherency on a multicore x86 CPU? is a good read regarding this topic.

I would say simply have a common lock ( java.util.concurrent.Lock or synchronized ) for the two variables and be done with it.


The Java Language Specification, Third Edition, has this to say about volatile:

8.3.1.4 volatile Fields

A field may be declared volatile, in which case the Java memory model (§17) ensures that all threads see a consistent value for the variable.

and

17.4.4 Synchronisation order

  • A write to a volatile variable (§8.3.1.4) v synchronizes-with all subsequent reads of v by any thread (where subsequent is defined according to the synchronization order).
  • A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

It says nothing about the visibility effect of other variables.

Community
  • 1
  • 1
Robert Munteanu
  • 67,031
  • 36
  • 206
  • 278
  • 2
    Sorry, this comment isn't true at all, as of Java 1.5. "The volatile read only guarantees that a reading thread sees the most recent value of the variable" is not correct. As of 1.5 a volatile write 'happens-before' any subsequent to that variable, meaning that _everything_ visible to the writing thread is now visible to the reading thread. See e.g. http://www.ibm.com/developerworks/library/j-jtp03304/ under "New Guarantees for Volatile" -- Listing 1 there is basically exactly this example. 'values that were visible to A at the time that V was written are guaranteed now to be visible to B.' – Cowan Aug 31 '09 at 05:30
  • @Cowan: I checked the JLS 3rd edition and found nothing regarding this. Can you please give an authoritative source for your statement? – Robert Munteanu Aug 31 '09 at 06:47
  • 2
    Robert, this is result of transitivity of happens-before relation. When serviceReady is true, it must have been modified by setService(). This establishes happens-before between volatile write and read. Actions before write and actions after read are therefore also in 'happens-before' relation. In other words, once you establish happens-before relation by some synchronization means, ALL changes are guaranteed to be visible. (17.4.5 Happens-before Order) – Peter Štibraný Aug 31 '09 at 07:19
  • 1
    For another writeup (again, using basically the same example as the provided code) see http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile -- the authors, Jeremy Manson and Brian Goetz, were the authors of the JSR for new Memory Model so I think we can consider them fairly authoritative. – Cowan Aug 31 '09 at 07:49
  • 1
    To expand on Peter's explanation, take the 3 points from 17.4.5: (1) "If x and y are actions of the same thread and x comes before y in program order, then hb(x, y)", (2) "A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.", and (3) "If hb(x, y) and hb(y, z), then hb(x, z)". So the write to 'service' happens-before the write to 'serviceReady' (first rule), the write to serviceReady happens-before the read of same (second rule), and we can infer that the write to service then happens-before the read of serviceReady (3rd rule). – Cowan Aug 31 '09 at 07:53
  • Probably worth breaking out these comments into a separate answer so it's more visible and can be voted on. Will do this now. – Cowan Aug 31 '09 at 07:59
  • _Now_ I get it. I'll leave the answer since the discussion thread is quite enlightening, but the 'accept' should be removed. – Robert Munteanu Aug 31 '09 at 09:33