无法理解Java中volatile的语义

时间:2012-09-22 02:55:21

标签: java volatile

我一直在阅读有关在Java中使用volatile变量的信息。据我所知,他们确保在不同核心/处理器上的系统中运行的所有线程的最新更新即时可见。但是,确保不会导致导致这些更新的操作的原子性。我看到经常使用以下文献

  

在每次读取同一字段之前,会发生对易失性字段的写入。

这是我有点困惑的地方。这是一段代码,可以帮助我更好地解释我的查询。

volatile int x = 0;
volatile int y = 0; 

Thread-0:                       |               Thread-1:
                                |
if (x==1) {                     |               if (y==1) {
     return false;              |                    return false; 
} else {                        |               } else {
     y=1;                       |                   x=1;
     return true;               |                   return true;
}                               |               }

自x& y都是volatile,我们在边缘之前发生了以下事情

  1. 在Thread-0中写入y和在Thread-1中读取y之间
  2. 在Thread-1中写入x和在Thread-0中读取x
  3. 之间

    这是否意味着,在任何时候,只有一个线程可以在其“块”块中(因为写入会在读取之前发生)?

    很可能Thread-0启动,加载x,找到它的值为0,就在它要在else-block中写y之前,有一个上下文切换到Thread-1,加载y找到它值为0,因此也进入else块。 volatile是否会阻止此类上下文切换(似乎不太可能)?

7 个答案:

答案 0 :(得分:5)

所以我认为这个问题有点杂草,要点是volatile表示变量的值可能会在当前线程的范围之外发生变化,并且必须始终读取其值使用

原则上,你引用的语句实际上是说在用当前线程替换值之前,将读取该值。

你的例子是一个竞争条件,两个线程都可能返回true,也不会返回true,或者它们可能各自返回一个不同的值 - volatile的语义不会为你的例子定义执行(我' d鼓励你编译并运行它,看看输出是否有所不同。

说明volatile行为的一种常用方法是运行两个线程,其中一个线程更新共享状态,并查看标记字段时发生的情况,以及何时不发生:

class VolatileTest implements Runnable
{
        // try with and without volatile
        private volatile boolean stopRunning = false;

        public void triggerStop(){
             stopRunning = true;
        }

        @Override
        public void run(){
             while(!stopRunning);
             System.out.println("Finished.");
        }

        public static void main (String[] args) throws java.lang.Exception
        {
            final VolatileTest test = new VolatileTest();
            new Thread(test).start();
            Thread.sleep(1000);
            test.triggerStop() = false;
        }
}

在此示例中,未将stopRunning标记为volatile可导致while循环永久延续,因为除非stopRunning被标记为volatile,否则不需要在每次迭代时读取值。

答案 1 :(得分:2)

易变的语义

您所指的问题是Dekker’s Algorithm的变体。有关google关于不同实现的详细信息以及有关它的详细信息。

  

如果两个进程同时尝试进入一个关键部分,该算法将只允许一个进程,基于轮到谁。如果一个进程已经在关键部分,则另一个进程将忙于等待第一个进程退出。这是通过使用两个标志flag [0]和flag [1]来完成的,这两个标志表示进入临界区的意图和一个表示谁在两个进程之间具有优先权的转弯变量。

维基百科涵盖volatile与Dekker's Alogrithm的相关性

易变信息

但我发现article只用一句话就完整地解释了volatile

  

如果变量被声明为volatile,则保证读取该字段的任何线程都将看到最近写入的值。 (Lars Vogel,2008)

     

本质上,volatile用于表示变量的值将被不同的线程修改。 (javamex,2012)

梅西大学:并发易失性讲座幻灯片

Massey Lecture Slide http://iforce.co.nz/i/zbntvu15.jcz.png 资料来源:Professor Hans W. Guesgen

如果您还不了解volatile,请查看atomicity的工作原理。

我希望这有帮助! :)

答案 2 :(得分:1)

  

这是否意味着,在任何时候,只有一个线程可以在其“块”块中(因为写入会在读取之前发生)?

不,不。

  

是否可以防止此类上下文切换(似乎不太可能)?

不,它没有防范这一点。您确定的竞争条件确实存在。

这表明挥发物不是通用的同步机制。相反,它们允许您在某些情况下避免同步,这些情况围绕可在单个内存操作中读取或写入的单个变量。

在您的示例中,有两个变量......

答案 3 :(得分:0)

我认为你这里有一个不好的例子。两个线程都可以很容易地进入else块(线程0读取x&检查1,然后线程1读取y并检查1)。

感兴趣的例子是这样的:

thread 0          thread 1

x = 1             y = 1
if (y == 0) {     if (x == 0) {
}                 }

如果ifx不稳定,则两个线程都无法进入y阻止。

答案 4 :(得分:0)

从Java语言规范中,volatile字段具有以下属性:

  

字段可以声明为volatile,在这种情况下,线程必须协调它   每次访问变量时,使用主副本的字段的工作副本。   而且,对一个或多个volatile变量的主副本的操作   代表一个线程是由主内存完全按顺序执行的   请求线程。

从上面,

  

这是否意味着,在任何时候,只有一个线程可以在其“块”块中(因为写入会在读取之前发生)?

这不会发生,因为volatile确保线程正在读取内存中变量的最新值,而不是来自它所拥有的本地副本。通过将变量声明为volatile来阻止阻塞。您应该使用synchronized概念来序列化对变量的写入。

答案 5 :(得分:0)

所有变量的所有易失性读写都按总顺序排列(同步顺序)。

在所有后续读取v的任何线程(其中“后续”根据同步顺序定义)之前发生对易失性变量v的写入

http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.4

答案 6 :(得分:0)

  

易失性保证可见性,同步保证可见性和原子性

只保证它提供的是,如果xy由一个线程更新,则所有线程都可以看到更新。 执行顺序仅取决于线程的运行方式。上下文切换问题的答案是否定的,因为在进入之前进行比较所以它只有一次并且它将基于当时变量的值。如果您对同一个变量进行多次比较,则可能会这样做。

使用volatile的最佳方法是维护类的状态变量。任何新任务中的位置将反映在访问该类的所有线程中

如果查看ThreadPoolExecutor的源代码,则有几个状态变量是易失性的。

其中一个恰好是runState,它确定了ThreadPoolExecutor的状态。现在调用shutdown()runState更新为SHUTDOWN,以便接受execute submit任务的方法停止接受恰好是{简单的if(runState==RUNNING)

以下代码是[{3}}

execute方法

在下面的代码中,如果您发现poolSizerunState变量不需要其他同步,因为它们是易失性状态变量。

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (**poolSize** >= corePoolSize || !addIfUnderCorePoolSize(command)) {//poolSize  volatile
        if (**runState** == RUNNING && workQueue.offer(command)) {//runstate volatile
            if (**runState** != RUNNING || **poolSize** == 0)//runstate volatile
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
  }

何时不使用?

  

当需要在原子操作中使用变量时,或者当一个变量值依赖于另一个变量值时,例如递增i ++。

<强>替代

  

原子变量被称为更好的易失性,其内存语义为volatile,但为您提供了额外的操作。例如incrementAndGet()操作由AtomicInteger提供。