什么时候在Java中使用volatile关键字?

时间:2010-08-15 18:36:17

标签: java multithreading volatile

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

7 个答案:

答案 0 :(得分:105)

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

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缓存等无效,因此可能会出现微小的性能下降,但编译器可能再次证明多个线程无法访问您的字段并删除{完全{1}}关键字并将其编译为相同的指令。

修改
回复此评论:

  

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

你可以,它会表现正常。 volatile可以使用volatile完成任何操作,但反之亦然。如果可以,您可能会选择synchronized,原因有两个:

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

答案 1 :(得分:14)

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

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

答案 2 :(得分:6)

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

来自java并发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文章

答案 3 :(得分:5)

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

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

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

答案 4 :(得分:3)

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

在提供的示例中,主线程可以继续打印"今天的温度为0"永远即使有另一个线程正在运行,如果其他线程永远不会被安排,那么它应该更新温度。

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

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

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

// Insert mobile menu before the Genesis Header Right widget navigation menu
$( '<div id="header-mobile-menu">&#x2261; <div class="mH"></div></div>' ).insertBefore( 'nav.nav-header ul.genesis-nav-menu' );

// Add .hide class to .nav-header .genesis-nav-menu to hide it for small screen sizes
$( 'nav.nav-header ul.genesis-nav-menu' ).addClass( 'hide' );

// Toggle Header Right widget navigation menu for mobile menu
$('#header-mobile-menu').on( 'click', function() {
    $('nav.nav-header ul.genesis-nav-menu').slideToggle();
    $(this).toggleClass('active');
});

    if($(window).width() < 992 ){
// Hide Header Right widget navigation menu for mobile menu if clicked
    $('.menu-item').on( 'click', function() {
    $('nav.nav-header ul.genesis-nav-menu').slideToggle();
    $('#header-mobile-menu').toggleClass('hide');
})};

最后两个赋值指令可以 NOT 重新排序,以便编译器,运行时或硬件进行优化。

});

一旦主线程读取易变温度(在打印过程中),

1)保证它将看到此volatile变量的最近写入的值,无论有多少线程正在写入它,无论它们将更新它的方法,同步或不。

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

时打印线程2中设置的值

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

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

答案 5 :(得分:2)

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

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

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

答案 6 :(得分:1)

voltalie表示保持更改值。此变量的值永远不会被线程本地缓存:所有读取和写入都将直接进入“主内存”。换句话说,Java编译器和线程不会缓存此变量的值并且总是从主存储器中读取它。