在Java中什么时候需要使用volatile关键字?
在Java中什么时候需要使用volatile关键字?
这个问题在这里已经有了答案:
我已经阅读过何时在Java中使用“volatile”?但我仍感到困惑。我该如何知道何时应该将变量标记为volatile?如果我标记错误,无论是在需要时省略了volatile,还是在不需要volatile时加上了它,会发生什么?在多线程代码中解决哪些变量应该是volatile的时候有什么经验法则?
在无锁算法中,volatile是最有用的。当您没有使用锁来访问保存共享数据的变量,并且您希望一个线程所做的更改对另一个线程可见,或者您希望创建一个“happens-after”关系以确保计算不会被重新排序,从而确保更改在适当的时间变得可见时,您可以将该变量标记为volatile。
JMM Cookbook描述了哪些操作可以重新排序,哪些操作不能重新排序。
当你想让成员变量被多个线程访问,但不需要复合原子性时(不确定这是否是正确的术语),可以使用它。
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
的更改并不一定对主线程“可见”。
基本上这意味着,如果不使用volatile
,你的应用程序甚至可能一直写入今天的温度为0
,如果你不使用volatile
,这种情况是可能发生的(在实践中,该值往往最终变得可见)。但是,当必要时,你不应冒不使用volatile所带来的风险,因为这可能会导致严重的错误(由于“未完全构建的对象”等引起的)。
如果在不需要volatile
的地方放置volatile
关键字,它不会影响你的代码的正确性(即行为不会改变)。在性能方面,它将取决于JVM实现。理论上,你可能会因为编译器无法重排序优化、必须使CPU缓存失效等而获得微小的性能降级,但是同样的,编译器可以证明你的字段不会被多个线程访问,并完全移除volatile
关键字的效果,并编译成相同的指令。
编辑:
回应此评论:
好的,但为什么我们不能将
todaysTemperature
同步化并创建一个同步的获取temperature
的方法?
你可以这样做,它会正确运作。你可以用volatile
实现的任何事情都可以用synchronized
完成,但反过来不行。有两个原因你可能更喜欢volatile
(如果可以的话):
- 较少的错误倾向:这取决于上下文,但在许多情况下,使用
volatile
比使用锁更不容易出现并发错误,如持有锁时阻塞、死锁等。 - 更高的性能:在大多数JVM实现中,
volatile
的吞吐量和延迟可能显着更高。然而,在大多数应用程序中,差异过小而不值得关注。