Using volatile keyword with mutable object
In Java, I understand that volatile
keyword provides visibility to variables. The question is, if a variable is a reference to a mutable object, does volatile
also provide visibility to the members inside that object?
In the example below, does it work correctly if multiple threads are accessing开发者_如何学编程 volatile Mutable m
and changing the value
?
example
class Mutable {
private int value;
public int get()
{
return a;
}
public int set(int value)
{
this.value = value;
}
}
class Test {
public volatile Mutable m;
}
This is sort of a side note explanation on some of the details of volatile. Writing this here because it is too much for an comment. I want to give some examples which show how volatile affects visibility, and how that changed in jdk 1.5.
Given the following example code:
public class MyClass
{
private int _n;
private volatile int _volN;
public void setN(int i) {
_n = i;
}
public void setVolN(int i) {
_volN = i;
}
public int getN() {
return _n;
}
public int getVolN() {
return _volN;
}
public static void main() {
final MyClass mc = new MyClass();
Thread t1 = new Thread() {
public void run() {
mc.setN(5);
mc.setVolN(5);
}
};
Thread t2 = new Thread() {
public void run() {
int volN = mc.getVolN();
int n = mc.getN();
System.out.println("Read: " + volN + ", " + n);
}
};
t1.start();
t2.start();
}
}
The behavior of this test code is well defined in jdk1.5+, but is not well defined pre-jdk1.5.
In the pre-jdk1.5 world, there was no defined relationship between volatile accesses and non-volatile accesses. therefore, the output of this program could be:
- Read: 0, 0
- Read: 0, 5
- Read: 5, 0
- Read: 5, 5
In the jdk1.5+ world, the semantics of volatile were changed so that volatile accesses affect non-volatile accesses in exactly the same way as synchronization. therefore, only certain outputs are possible in the jdk1.5+ world:
- Read: 0, 0
- Read: 0, 5
- Read: 5, 0 <- not possible
- Read: 5, 5
Output 3. is not possible because the reading of "5" from the volatile _volN establishes a synchronization point between the 2 threads, which means all actions from t1 taken before the assignment to _volN must be visible to t2.
Further reading:
- Fixing the java memory model, part 1
- Fixing the java memory model, part 2
In your example the volatile
keyword only guarantees that the last reference written, by any thread, to 'm' will be visible to any thread reading 'm' subsequently.
It doesn't guarantee anything about your get().
So using the following sequence:
Thread-1: get() returns 2
Thread-2: set(3)
Thread-1: get()
it is totally legitimate for you to get back 2 and not 3. volatile
doesn't change anything to that.
But if you change your Mutable
class to this:
class Mutable {
private volatile int value;
public int get()
{
return a;
}
public int set(int value)
{
this.value = value;
}
}
Then it is guaranteed that the second get()
from Thread-1 shall return 3.
Note however that volatile
typically ain't the best synchronization method.
In you simple get/set example (I know it's just an example) a class like AtomicInteger
, using proper synchronization and actually providing useful methods, would be better.
volatile
only provides guarantees about the reference to the Object that is declared so. The members of that instance don't get synchronized.
According to the Wikipedia, you have:
- (In all versions of Java) There is a global ordering on the reads and writes to a volatile variable. This implies that every thread accessing a volatile field will read its current value before continuing, instead of (potentially) using a cached value. (However, there is no guarantee about the relative ordering of volatile reads and writes with regular reads and writes, meaning that it's generally not a useful threading construct.)
- (In Java 5 or later) Volatile reads and writes establish a happens-before relationship, much like acquiring and releasing a mutex.
So basically what you have is that by declaring the field volatile
, interacting with it creates a "point of synchronization", after which any change will be visible in other threads. But after that, using get()
or set()
is unsynched. The Java Spec has a more thorough explanation.
Use of volatile
rather than a fully synchronized
value is essentially an optimization. The optimization comes from the weaker guarantees provided for a volatile
value compared with a synchronized
access. Premature optimmization is the root of all evil; in this case, the evil could be hard to track down because it would be in the form of race conditions and such like. So if you need to ask, you probably ought not to use it.
volatile
does not "provide visibility". Its only effect is to prevent processor caching of the variable, thus providing a happens-before relation on concurrent reads and writes. It does not affect the members of an object, nor does it provide any synchronisation synchronized
locking.
As you haven't told us what the "correct" behaviour of your code is, the question cannot be answered.
精彩评论