什么时候你在Java中使用volatile关键字?

我读过“ 什么时候在Java中使用'volatile'? ”但我仍然感到困惑。 我如何知道什么时候应该标记variablesvolatile? 如果我弄错了,或者在需要它的东西上忽略一些不稳定的东西,或者把一些不稳定的东西放在那些不需要的东西上? 在计算multithreading代码中哪些variables应该是易变的时,有哪些经验法则?

当你想让一个成员variables被多个线程访问,但不需要复合primefaces性(不知道这是否是正确的术语)时,你基本上使用它。

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++; } } 

以上是一个不好的例子,因为你需要复合的primefaces性。

  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 (实际上,这个值最终会变成可见的),那么Today's temperature is 0永远Today's temperature is 0 ,但是你不应该冒险在必要的时候不使用volatile,因为它可能导致令人讨厌的bug完全构build的对象等)。

如果你把volatile关键字放在不需要volatile东西上,它不会影响你的代码的正确性(即行为不会改变)。 在性能方面,它将取决于JVM的实现。 从理论上讲,性能可能会因为编译器无法对优化进行重新sorting而导致性能下降,不得不使CPUcaching等无效,但是编译器会再次certificate您的字段永远无法被多个线程访问,并消除volatile关键字,并将其编译为相同的指令。

编辑:
回应这个评论:

好的,但为什么我们不能让今天的温度同步,并创build一个温度同步的吸气剂?

你可以,它会performance正确。 任何你可以用volatile都可以完成的,但是反之亦然。 如果可以的话,您可能更喜欢volatile两个原因:

  1. 减less错误:这取决于上下文,但在很多情况下,使用volatile不太容易发生并发性错误,比如持有锁的阻塞,死锁等。
  2. 更高性能:在大多数JVM实现中, volatile可以有更高的吞吐量和更好的延迟。 但是在大多数应用中,差别太小而不重要。

易失性在无锁algorithm中最为有用。 当您不使用locking来访问该variables并且您希望一个线程所做的更改在另一个线程中可见时,或者您想要创build“发生后续”关系以确保计算是而不是重新sorting,以确保在适当的时候可以看到变化。

JMM Cookbook描述了哪些操作可以重新sorting,哪些不能。

volatile还可用于在multithreading环境中安全地发布不可变对象。

声明像public volatile ImmutableObject foo这样的字段可以确保所有线程始终能够看到当前可用的实例引用。

有关该主题的更多信息,请参阅实践中的Java Concurrency 。

实际上不同意顶部投票答案中给出的例子,据我所知,它不能正确地说明根据Java内存模型的易失性语义。 易失性有更复杂的语义。

在提供的示例中,即使有另一个线程正在运行,主线程仍可以继续打印“今天的温度为0”,如果另一个线程永远不会被调度,则该线程应该更新温度。

说明易失性语义的更好方法是使用2个variables。

为简单起见,我们将假设更新两个variables的唯一方法是通过“setTemperatures”方法。

为了简单起见,我们假设只有2个线程正在运行,主线程和线程2。

 //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 ); } } 

一旦主线程读取易变的温度(在打印过程中),

1)可以保证,无论有多less个线程正在写入,它都将看到这个易失性variables的最近写入的值,而不pipe它们正在更新哪个方法,是否同步。

2)如果主线程中的system.out语句运行, 线程2运行的时间点temperature = temp之后,昨天的温度和今天的温度将被保证在线程2中打印设置的值它运行温度=温度的陈述。

如果a)multithreading正在运行,并且b)除了setTemperatures方法外,还有其他一些方法可以更新昨天的温度和当前正在被其他线程调用的温度。 我认为这将需要一个体面的大小的文章来分析的基础上如何Java存储模型描述易失性语义的影响。

简而言之,试图仅仅使用volatile来进行同步是非常危险的,你最好坚持同步你的方法。

volatile 关键字保证volatilevariables的值总是从主内存中读取,而不是从Thread的本地caching中读取。

从java并发教程 :

使用volatilevariables可以降低内存一致性错误的风险,因为任何对volatilevariables的写操作都会build立一个before-before关系,并且随后读取同一个variables

这意味着对其他线程总是可见的对volatilevariables的更改。 这也意味着当一个线程读取一个volatilevariables时,它不仅会看到volatile的最新变化,还会看到导致变化的代码的副作用。

关于你的查询:

我如何知道什么时候应该标记variablesvolatile? 在计算multithreading代码中哪些variables应该是易变的时,有哪些经验法则?

如果你觉得所有的读者线程总是得到一个variables的最新值,你必须将variables标记为volatile

如果您有一个编写器线程修改variables值和多个读取器线程来读取variables的值 ,那么volatile修饰符可以保证内存一致性。

如果您有多个线程来编写和读取variables,单独使用volatile修饰符不能保证内存一致性。 您必须synchronize代码或使用高级并发结构,如LocksConcurrent CollectionsAtomic variables

相关的SE问题/文章:

Java文档中的易变variables说明

Java中volatile和synchronized的区别

javarevisited文章

http://mindprod.com/jgloss/volatile.html

“volatile关键字用于可能被其他线程同时修改的variables。”

“由于其他线程无法看到局部variables,所以永远不需要将局部variables标记为volatile,你需要同步来协调对来自不同线程的variables的变化,但往往不稳定只会看到它们。