当你想让一个成员变量被多个线程访问但不需要复合原子性(不确定这是否是正确的术语)时,你基本上使用它.
class BadExample {
private volatile int counter;
public void hit(){
/* This operation is in fact two operations:
* 1) int tmp = this.counter;
* 2) this.counter = tmp + 1;
* and is thus broken (counter becomes fewer
* than the accurate amount).
*/
counter++;
}
}
以上是一个不好的例子,因为你需要复合原子性.
class BadExampleFixed {
private int counter;
public synchronized void hit(){
/*
* Only one thread performs action (1), (2) at a time
* "atomically", in the sense that other threads can not
* observe the intermediate state between (1) and (2).
* Therefore, the counter will be accurate.
*/
counter++;
}
}
现在来看一个有效的例子:
class GoodExample {
private static volatile int temperature;
//Called by some other thread than main
public static void todaysTemperature(int temp){
// This operation is a single operation, so you
// do not need compound atomicity
temperature = temp;
}
public static void main(String[] args) throws Exception{
while(true){
Thread.sleep(2000);
System.out.println("Today's temperature is "+temperature);
}
}
}
现在,为什么你不能只使用私有静态int温度?实际上你可以(从某种意义上说你的程序不会爆炸或者其他东西),但是其他线程对温度的改变可能对主线程“可见”,也可能不是“可见”.
基本上这意味着你的应用甚至可能.如果你不使用挥发性物质,那么今天的温度永远是0(实际上,这个值最终会变得可见.但是,你不应该冒险在必要时不使用挥发性物质,因为它可能导致令人讨厌的错误(由于 – 完全构造的物体等).
如果将volatile关键字放在不需要volatile的东西上,它不会影响代码的正确性(即行为不会改变).在性能方面,它将取决于JVM实现.从理论上讲,由于编译器无法进行重新排序优化,不得不使CPU缓存等无效,因此可能会出现性能下降,但编译器可能再次证明您的字段永远不会被多个线程访问并消除volatile的影响完全关键字并将其编译为相同的指令.
编辑:
回复此评论:
Ok, but why can’t we make todaysTemperature synchronized and create a synchronized getter for temperature?
你可以,它会表现正常.使用volatile可以完成的任何事情都可以通过同步来完成,但反之亦然.如果可以的话,有两个原因你可能更喜欢易变:
>减少错误:这取决于上下文,但在许多情况下使用volatile不太容易出现并发错误,比如在持有锁时阻塞,死锁等.
>更高性能:在大多数JVM实现中,volatile可以具有更高的吞吐量和更好的延迟.然而,在大多数应用中,差异太小而无关紧要.