我读过 "Java中何时使用'volatile'?"但仍感到困惑。我如何知道何时应将变量标记为volatile?如果我弄错了,要么省略了某个需要它的volatile,要么将volatile放在不需要的东西上怎么办?在多线程代码中确定哪些变量应该是volatile时有什么经验法则?
我读过 "Java中何时使用'volatile'?"但仍感到困惑。我如何知道何时应将变量标记为volatile?如果我弄错了,要么省略了某个需要它的volatile,要么将volatile放在不需要的东西上怎么办?在多线程代码中确定哪些变量应该是volatile时有什么经验法则?
当你想让一个成员变量被多个线程访问,但不需要复合原子性时(不确定这是否是正确的术语),你基本上会使用它。
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
的代码上,它不会影响你的代码正确性(即行为不会改变)。在性能方面,这取决于JVM实现。理论上,由于编译器无法进行重排序优化并且必须使CPU缓存失效等,你可能会获得微小的性能下降,但随后编译器可能会证明你的字段永远不可能被多个线程访问,从而完全删除volatile
关键字并将其编译为相同的指令。volatile
完成的事情都可以用synchronized
完成,但反过来则不行。有两个原因你可能更喜欢volatile
:volatile
比使用锁具有更少的并发错误,如在持有锁时阻塞、死锁等。volatile
的吞吐量和延迟可以显著提高。然而,在大多数应用程序中,差异太小以至于无关紧要。synchronized
提供的保证是 volatile
提供保证的超集,因此在使用 synchronized
的情况下同时使用 volatile
是多余的。 - Enno Shiojivolatile最常用于无锁算法。当您不使用锁来访问存储共享数据的变量时,将该变量标记为volatile,并且您希望一个线程所做的更改在另一个线程中可见,或者您想创建“happens-after”关系以确保计算不会被重新排序,同样,以确保更改在适当的时间可见。
JMM Cookbook描述了哪些操作可以重新排序,哪些不能。
volatile
关键字保证了 volatile 变量的值始终从主内存中读取,而不是从线程的本地缓存中读取。
来自 Java 并发 tutorial:
使用 volatile 变量可以减少内存一致性错误的风险,因为对 volatile 变量的任何写操作都会与随后对该变量的读操作建立 happens-before 关系。
这意味着对 volatile 变量的更改始终对其他线程可见。这也意味着当线程读取 volatile 变量时,它不仅看到了 volatile 的最新更改,还看到了导致更改的代码的副作用。
关于您的查询:
我如何知道何时应将变量标记为 volatile?在多线程代码中找出应该是 volatile 的变量的经验法则是什么?
如果您认为所有读取器线程始终获取变量的最新值,则必须将变量标记为 volatile
如果您有一个写入器线程用于修改变量的值和多个读取器线程用于读取变量的值,则 volatile 修饰符保证内存一致性。
如果您有多个线程来读写变量,仅使用volatile
修饰符不能保证内存一致性。您必须同步代码或使用高级并发构造,如Locks
、Concurrent Collections
、Atomic variables
等。volatile
也可用于在多线程环境中安全地发布不可变对象。
声明像 public volatile ImmutableObject foo
这样的字段,确保所有线程始终看到当前可用的实例引用。
有关该主题的更多信息,请参见Java并发编程实践。//volatile variable
private static volatile int temperature;
//any other variable, could be volatile or not volatile doesnt matter.
private static int yesterdaysTemperature
//Called by other thread(s)
public static void setTemperatures(int temp, int yestemp){
//thread updates yesterday's temperature
yesterdaysTemperature = yestemp;
//thread updates today's temperature.
//This instruction can NOT be moved above the previous instruction for optimization.
temperature = temp;
}
无论是编译器、运行时还是硬件,最后两个任务指令都不能被重新排序以进行优化。
public static void main(String[] args) throws Exception{
while(true){
Thread.sleep(2000);
System.out.println("Today's temperature is "+temperature);
System.out.println("Yesterday's temperature was "+yesterdaysTemperature );
}
}
http://mindprod.com/jgloss/volatile.html
“volatile”关键字用于可能被其他线程同时修改的变量。
由于其他线程无法看到局部变量,因此永远不需要将局部变量标记为volatile。您需要使用synchronized来协调来自不同线程的变量更改,但通常仅需要使用volatile来查看它们。”
Volatile的意思是保持值的不断变化。这个变量的值永远不会在本地线程中缓存:所有读写操作都将直接进入“主内存”。换句话说,Java编译器和线程不会缓存此变量的值,而总是从主内存中读取它。