在 Java 中,您究竟何时使用 volatile 关键字?

2022-08-31 11:21:50

我已经读过“什么时候在Java中使用'volatile'?”但我仍然感到困惑。我如何知道何时应该标记可变变量?如果我弄错了,要么在需要它的东西上省略一个挥发性,要么在不需要它的东西上放一个挥发性,该怎么办?在弄清楚哪些变量在多线程代码中应该是易失性的时,经验法则是什么?


答案 1

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

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 temperaturetemperature

基本上,这意味着如果你不使用,你的应用程序甚至有可能永远保持写作(在实践中,价值往往最终变得可见。但是,您不应该在必要时冒险不使用易失性,因为它可能导致令人讨厌的错误(由完全构造的对象等引起)。Today's temperature is 0volatile

如果你在不需要的东西上加上关键字,它不会影响你的代码的正确性(即行为不会改变)。在性能方面,它将取决于JVM的实现。从理论上讲,您可能会得到一个很小的性能下降,因为编译器无法进行重新排序优化,必须使CPU缓存失效等,但随后编译器可以证明您的字段不能被多个线程访问,并完全删除关键字的影响并将其编译为相同的指令。volatilevolatilevolatile

编辑:
对此评论的回应:

好吧,但是为什么我们不能使今天的温度同步并创建一个同步的温度获取器呢?

你可以,它会正确运行。任何你能用的东西都可以用 来完成,但反之亦然。如果可以的话,您可能更喜欢两个原因:volatilesynchronizedvolatile

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

答案 2

易失性在无锁算法中最有用。当您不使用锁定来访问保存共享数据的变量,并且希望一个线程所做的更改在另一个线程中可见时,或者您希望创建“发生后”关系以确保计算不会重新排序,以确保更改在适当的时间变得可见时,请将保存共享数据的变量标记为易失性。

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