当你想让一个成员变量被多个线程访问但不需要复合原子性(不确定这是否是正确的术语)时,你基本上使用它。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);
}
}}
现在,为什么你不能使用private static int temperature?实际上你可以(在某种意义上说你的程序不会爆炸或者其他东西),但是temperature另一个线程的改变可能对主线程“可见”,也可能不是“可见”。
基本上这意味着你的应用甚至可能。Today's temperature is 0如果你不使用volatile,它会永远写作(在实践中,这个值最终会变得可见。但是,你不应该冒险在必要时不使用volatile,因为它可能会导致令人讨厌的错误(由完全构造的对象等引起)。 )。
如果将volatile关键字放在不需要的东西上volatile,它不会影响代码的正确性(即行为不会改变)。在性能方面,它将取决于JVM实现。从理论上讲,由于编译器无法进行重新排序优化,不得不使CPU缓存等无效,因此可能会出现微小的性能下降,但再次编译器可以证明多个线程无法访问您的字段并删除volatile关键字的影响完全并将其编译为相同的指令。
编辑:
回复此评论:好的,但是为什么我们不能让todaysTemperature同步并为温度创建一个同步的getter?
你可以,它会表现正常。您可以使用的任何东西volatile都可以完成synchronized,但反之亦然。volatile如果可以,您可能会有两个理由:减少错误:这取决于上下文,但在许多情况下使用volatile不太容易出现并发错误,例如在持有锁时阻塞,死锁等。
性能volatile更高:在大多数JVM实现中,可以具有更高的吞吐量和更好的延迟。然而,在大多数应用中,差异太小而无关紧要。