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();
}
}
Volatile keyword is used to modify the value of a variable by different threads. It is also used to make classes thread safe. It means that multiple threads can use a method and instance of the classes at the same time without any problem. The volatile keyword can be used either with primitive type or objects.
So where volatile only synchronizes the value of one variable between thread memory and "main" memory, synchronized synchronizes the value of all variables between thread memory and "main" memory, and locks and releases a monitor to boot. Clearly synchronized is likely to have more overhead than volatile.
Volatile variables have the visibility features of synchronized but not the atomicity features. The values of the volatile variable will never be cached and all writes and reads will be done to and from the main memory.
Volatile is a qualifier that is applied to a variable when it is declared. It tells the compiler that the value of the variable may change at any time-without any action being taken by the code the compiler finds nearby.
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:
So in your example:
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.
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();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With