什么时候在Java中使用volatile关键字? [重复]

问题描述 投票:84回答:7

这个问题在这里已有答案:

我读过“When to use 'volatile' in Java?”,但我仍然感到困惑。我怎么知道何时应该标记变量volatile?如果我弄错了,要么在需要它的东西上省略volatile,要么在不需要的东西上放置volatile呢?在确定多线程代码中哪些变量应该是易变的时,有哪些经验法则?

java multithreading volatile
7个回答
106
投票

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

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

  1. 减少错误:这取决于上下文,但在许多情况下使用volatile不太容易出现并发错误,例如在持有锁时阻塞,死锁等。
  2. 性能更高:在大多数JVM实现中,volatile可以具有更高的吞吐量和更好的延迟。然而,在大多数应用中,差异太小而无关紧要。

14
投票

易失性在无锁算法中最有用。当您不使用锁定来访问该变量并且您希望一个线程所做的更改在另一个线程中可见时,或者您希望创建“发生后”关系以确保计算是不再重新订购,以确保在适当的时间可见变化。

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


6
投票

volatile关键字保证volatile变量的值将始终从主内存中读取,而不是从Thread的本地缓存中读取。

来自java concurrency tutorial

使用volatile变量可以降低内存一致性错误的风险,因为对volatile变量的任何写入都会建立与之后读取相同变量的先发生关系

这意味着对volatile变量的更改始终对其他线程可见。它还意味着当线程读取volatile变量时,它不仅会看到volatile的最新更改,还会看到导致更改的代码的副作用。

关于你的查询:

我怎么知道何时应该标记变量volatile?在确定多线程代码中哪些变量应该是易变的时,有哪些经验法则?

如果您认为所有读者线程始终获得变量的最新值,则必须将变量标记为volatile

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

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

相关的SE问题/文章:

Volatile variable explanation in Java docs

Difference between volatile and synchronized in Java

javarevisited文章


5
投票

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

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

有关该主题的更多信息,请参阅Java Concurrency in Practice


3
投票

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

在提供的示例中,主线程可以继续打印“今天的温度为0”,即使存在另一个运行的线程,如果其他线程从未被调度,则应该更新温度。

说明volatile语义的更好方法是使用2个变量。

为简单起见,我们假设更新两个变量的唯一方法是通过方法“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)保证它将看到此volatile变量的最近写入的值,无论有多少线程正在写入它,无论它们更新它,同步与否的方法。

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

如果a)多个线程正在运行并且b)除了setTemperatures方法之外还有其他方法可以更新昨天的温度和今天的温度,这些方法正在被这些其他线程主动调用,这种情况变得更加复杂。我认为根据Java内存模型如何描述易失性语义来分析其含义需要一篇不错的文章。

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


2
投票

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

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

“由于其他线程无法看到局部变量,因此永远不需要将局部变量标记为volatile。您需要同步以协调对来自不同线程的变量的更改,但通常volatile只会查看它们。”


2
投票

voltalie意味着保持不变的值。这个变量的值永远不会被线程本地缓存:所有的读写都会直接进入“主存”。换句话说,Java编译器和线程不会缓存这个变量的值并且总是读取它来自主存。

© www.soinside.com 2019 - 2024. All rights reserved.