【问题标题】:When exactly do you use the volatile keyword in Java? [duplicate]你什么时候在 Java 中使用 volatile 关键字? [复制]
【发布时间】:2011-03-30 03:46:33
【问题描述】:

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

【问题讨论】:

标签: java multithreading volatile


【解决方案1】:

voltalie 意味着不断变化的值。这个变量的值永远不会被缓存在线程本地:所有的读取和写入都将直接进入“主内存”。换句话说,Java 编译器和线程不缓存这个变量的值并始终从主内存中读取它。

【讨论】:

    【解决方案2】:

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

    来自java并发tutorial

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

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

    关于您的查询:

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

    如果你觉得所有的reader线程总是得到一个变量的最新值,你必须把变量标记为volatile

    如果你有一个写线程来修改变量的值,多个读线程来读取变量的值,volatile修饰符保证了内存的一致性。

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

    相关的 SE 问题/文章:

    Volatile variable explanation in Java docs

    Difference between volatile and synchronized in Java

    javarevisited文章

    【讨论】:

      【解决方案3】:

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

      在提供的示例中,主线程可以继续打印“今天的温度为 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 );
       }
      }
      

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

      1) 保证它会看到这个 volatile 变量的最近写入 值,无论有多少线程正在写入它,无论它们使用哪种方法更新它,同步与否。

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

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

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

      【讨论】:

      • 您的示例也可能有助于解释使用同步可能更好的地方。那些打印语句可以在另一个线程执行“yesterdaysTemperature = yestemp”但尚未执行“temperature = temp”时运行,不是吗?
      【解决方案4】:

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

      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,就会一直写Today's temperature is 0(在实践中,值往往会最终变得可见。但是,你不应该冒险在必要时不使用volatile,因为它可能导致讨厌的错误(由未完全构造的对象等引起)。

      如果你把 volatile 关键字放在不需要 volatile 的东西上,它不会影响你的代码的正确性(即行为不会改变)。在性能方面,它将取决于 JVM 实现。从理论上讲,您可能会因为编译器无法进行重新排序优化、必须使 CPU 缓存无效等而导致性能略有下降,但是编译器可以再次证明您的字段不能被多个线程访问并消除 @ 的影响987654330@ 关键字完全并将其编译为相同的指令。

      编辑:
      对此评论的回应:

      好的,但是为什么我们不能让 todaysTemperature 同步并为温度创建一个同步的 getter?

      您可以并且它会正常运行。 volatile 可以做的任何事情都可以用synchronized 完成,反之则不行。如果可以的话,您可能更喜欢volatile 有两个原因:

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

      【讨论】:

      • 我经常看到这个问题,这是第一个对我来说非常有意义的答案。谢谢。
      • 好的,但是为什么我们不能让todaysTemperature同步并为temperature创建一个同步的getter?
      • @SemyonDanilov:在底部添加了答案。 HTH。
      • 对我来说,仍然不清楚同步的 getter 在这里会有什么帮助?如果不使用 volatile,我们仍然无法保证线程本地缓存。
      • @St.Antario:synchronized 提供的保证是volatile 提供的超集,因此除了synchronized 之外使用volatile 是多余的。
      【解决方案5】:

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

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

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

      【讨论】:

      • 所以我认为其中的“不可变”部分有些问题......请参阅我的答案:stackoverflow.com/questions/3964317/… 和 JSR-133 常见问题解答,项目 cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile。我认为更准确的说法是您可以安全地发布不需要其他同步的对象......我可能没有正确解决这个问题,但只要发生易失性写入,正常写入之前的同一个线程任何线程阅读都可以看到...
      • @andersoj 是正确的,波动性是可传递的。 a.b 的读取是易失的,a 的读取是易失的。对 a.b 的写入发生在对 a 的写入之前,如果它按程序顺序在它之前。因此 volatile 可用于安全地发布可变(否则为非线程安全)对象。我不认为这是一个特别好的想法,并且总是尽可能地宣扬不可变的对象,但这仍然是一个重要的点。
      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

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

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

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

        【讨论】:

          猜你喜欢
          • 2010-09-09
          • 2023-03-08
          • 2010-09-06
          • 2011-05-25
          • 2019-10-11
          • 1970-01-01
          相关资源
          最近更新 更多