关于多线程:什么时候在Java中使用volatile关键字?

When exactly do you use the volatile keyword in Java?

本问题已经有最佳答案,请猛点这里访问。

我读过"何时使用Java中的‘易失性’?"但我还是很困惑。如何知道何时标记变量volatile?如果我弄错了,要么在需要它的东西上省略一个易失性,要么在不需要它的东西上加上易失性?在确定多线程代码中哪些变量应该是易失变量时,经验法则是什么?


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

1
2
3
4
5
6
7
8
9
10
11
12
13
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++;
    }
}

上面的例子是一个浴室,因为你需要化合物的原子性。

1
2
3
4
5
6
7
8
9
10
11
12
13
 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++;
    }
}

现在一个有效的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 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,它不会影响您的代码的正确性(即行为不会改变)。性能方面,它想depend on the JVM的实现。你可能得到一个小的理论性能退化,因为编译器不能做reordering optimisations,要invalidate CPU缓存等,然后再在编译器可以证明你有多场无法加载和删除的线程访问的影响和它的volatile关键字完全相同的编译指示。

编辑:本评论反应:

Ok, but why can't we make todaysTemperature synchronized and create a synchronized getter for temperature?

你想和它的正确的行为。的东西,你可以与volatileCAN和synchronized,但不反之亦然。你可能有两个原因:如果你喜欢volatileCAN

  • 不易:这取决于错误的上下文,但在许多情况下是不使用一个volatile易并发错误,样的阻塞而控股锁,死锁等。
  • 在大多数的JVM的实现更多的性能:volatileCAN有显着高,高通量和更好的延迟。然而,在大多数应用差别太小到物。

  • 波动是最有用的在无锁算法。你的共享可变数据作为标记持有挥发性当你不使用锁定访问变量的变化是由一个和你想在另一个线程是可见的,或你想创建一个"后发生的"关系到确保不重新计算一次,确保有序,在可见的变化。适当的时间。

    《JMM食谱是运营了CAN的序,无法。


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

    从Java并发教程:

    Using volatile variables reduces the risk of memory consistency errors, because any write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable

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

    关于您的查询:

    How do I know when I should mark a variable volatile? What are the rules of thumb when figuring out what variables should be volatile in multithreaded code?

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

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

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

    相关SE问题/文章:

    Java文档中易失性变量的解释

    Java中易失性与同步性的区别

    javarevisited文章


    volatile也可以用来安全地发布不可变的对象在多线程环境。

    这是一场declaring样public volatile ImmutableObject foosecures所有目前可用的线程总是湖实例参考。

    Java并发湖在实践上更多的主题。


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

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

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

    为了简单起见,我们假设更新这两个变量的唯一方法是通过方法"setttemperatures"。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //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;
       }

    最后两个分配指令不能为优化目的被编译器、运行时或硬件重新排序。

    1
    2
    3
    4
    5
    6
    7
    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时打印线程2设置的值。

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

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


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

    "在这动荡的关键字是特定变量可能被修改由其他线程。

    "因为其他线程本地变量不能湖,那里没有任何需要标记的本地变量波动。你需要同步到坐标改变变量的波动往往从不同的线程,但想做只是看看他们。"


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