代码之家  ›  专栏  ›  技术社区  ›  Ricket

何时使用Java中的易失性关键字?[复制品]

  •  83
  • Ricket  · 技术社区  · 14 年前

    我读过 When to use 'volatile' in Java? “但我还是很困惑。如何知道何时标记变量volatile?如果我弄错了,或者在需要它的东西上省略一个易失性,或者在不需要它的东西上加上易失性,会怎么样?在确定多线程代码中哪些变量应该是易失变量时,经验法则是什么?

    7 回复  |  直到 5 年前
        1
  •  104
  •   Enno Shioji    10 年前

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

    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,因为它可能导致讨厌的错误(由完全构造的对象等引起)。

    如果你放 不稳定的 不需要的关键词 不稳定的 它不会影响代码的正确性(即行为不会改变)。在性能方面,它将取决于JVM的实现。理论上,由于编译器不能进行重新排序优化、必须使CPU缓存无效等,您可能会得到一个微小的性能下降,但是编译器再次证明您的字段不能被多个线程访问,并消除 不稳定的 关键字完整地编译成相同的指令。

    编辑:
    对此评论的回复:

    好吧,但是为什么我们不能使今天的温度同步化,并为温度创建一个同步的吸气剂呢?

    你可以,它会正常运转。任何你能用的东西 不稳定的 可以用 synchronized 反之亦然。你可能更喜欢两个原因 不稳定的 如果你能:

    1. 不易出错:这取决于上下文,但在许多情况下使用 不稳定的 不太容易出现并发错误,比如在持有锁的同时阻塞、死锁等。
    2. 更高性能:在大多数JVM实现中, 不稳定的 可以显著提高吞吐量和延迟。然而,在大多数应用中,差异太小,不重要。
        2
  •  14
  •   mdma    14 年前

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

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

        3
  •  6
  •   Community basarat    7 年前

    volatile 关键字保证volatile变量的值总是从主内存中读取,而不是从线程的本地缓存中读取。

    从Java并发 tutorial :

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

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

    关于您的查询:

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

    如果您觉得所有的读线程总是获得一个变量的最新值,那么您必须将该变量标记为 不稳定的

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

    如果您有多个线程来写入和读取变量, 不稳定的 仅修饰符不能保证内存的一致性。你必须 synchronize 代码或使用高级 concurrency 构造像 Locks , Concurrent Collections , Atomic variables 等。

    相关SE问题/文章:

    Volatile variable explanation in Java docs

    Difference between volatile and synchronized in Java

    javarevisited 文章

        4
  •  5
  •   Johannes Wachter    14 年前

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

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

    Java Concurrency in Practice 关于那个主题的更多信息。

        5
  •  3
  •   SR Bhaskar    9 年前

    事实上,我不同意上面投票的答案中给出的例子,据我所知,的确如此。 不是 根据Java内存模型适当地说明易失性语义。volatile的语义更加复杂。

    在所提供的示例中,主线程可以永远继续打印“今天的温度是0”,即使有另一个线程正在运行,如果另一个线程从未被调度过,那么它应该更新温度。

    用2个变量来说明易失性语义是一种更好的方法。

    为了简单起见,我们假设更新这两个变量的唯一方法是通过方法 “设置温度” .

    为了简单起见,我们假设只有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)有保证它将看到 最近写的 这个易失变量的值,不管有多少线程正在写入它,不管它们在哪个方法中更新它,是否同步。

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

    这种情况下 批次 如果a)多个线程正在运行,b)还有其他方法,而不仅仅是setttemperatures方法,可以更新变量昨天的温度和今天的温度,这些其他线程正在主动调用这些变量。我认为,根据Java内存模型如何描述易失性语义,将需要一个体面的文章来分析其含义。

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

        6
  •  2
  •   KristofMols    14 年前

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

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

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

        7
  •  1
  •   Akshay    6 年前

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