volatile变量如何与其他字段一起使用?

时间:2016-04-07 12:58:14

标签: java multithreading concurrency

有一种方法可以按顺序初始化三个变量:

 public class Counter implements Runnable{
     private int a;
     private int b;
     private volatile int c;

    //Other code in class goes here

    private void incrementCounter(int i){
      a=10+i;
      b=11+i;
      c=12+i;
     //some other code
     }

   }

incrementCounter(int i)方法由一个线程调用,在c变量初始化之后,主内存中a,b和c的值是什么?为什么?

4 个答案:

答案 0 :(得分:5)

您应该根据Java内存模型推断某些字段值的可见性和排序保证。

我会假设你的问题是要问“我对其他线程观察到的abc的值有什么保证? “

在这种情况下,为了从volatile语义中获得好处,您需要在另一个看到特定volatile写入的线程中读取volatile。如果发生这种情况,volatile读取同步,volatile在读取之前写入发生的事情,并且保证读取线程可以看到ab的值不早于写入c观察值的线程中写入的值。

P.S。当我说“不老”时,我是非正式的,因为JMM竭尽全力避免所有行动的全球时间排序。如果我们想要正式,我们可以使用同步顺序,并定义“没有旧”,因为在观察到的之前,同步顺序中的volatile写入不可见。

答案 1 :(得分:1)

简而言之:通过对volatile写入的volatile读取创建的事前发生关系确保了读取c值的人也将观察到ab的变化。

在随后每次对该字段进行读取之前,都会对volatile字段(第8.3.1.4节)进行写操作。 [...]如果hb(x,y)和hb(y,z),则hb(x,z)。

https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.4.5

[...]因为before-before关系是可传递的,所以解锁之前线程的所有操作都发生在监视该线程的线程锁定之后的所有操作之前。

[...]

写入和读取易失性字段与进入和退出监视器[。]

具有相似的内存一致性效果。

https://download.java.net/java/GA/jdk14/docs/api/java.base/java/util/concurrent/package-summary.html

此外,这还意味着当线程读取一个volatile变量时,它不仅会看到对volatile的最新更改,还会看到导致更改的代码的副作用。

https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html

写到易失性字段具有与监视器释放相同的存储效果,而从易失性字段进行读取具有与监视器获取相同的存储效果。实际上,由于新的内存模型对易失性字段访问与其他易失性字段访问的重新排序施加了更严格的约束,因此线程A写入易失性字段f时对线程A可见的任何内容在读取f时都对线程B可见。

https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#synchronization

同一页面继续构建一个非常类似于您自己的示例:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //uses x - guaranteed to see 42.
    }
  }
}

最后,要注意一些有趣的陷阱:

https://shipilev.net/blog/2016/close-encounters-of-jmm-kind/#pitfall-release-order-wrong

答案 2 :(得分:0)

发生什么取决于硬件平台和JVM实现。 必须发生的内容由Java语言规范(JLS)指定。

JLS说当线程U读取c时,其他一些线程U将能够看到所有三个更新

更具体地说,JLS表示对线程T中的volatile c的更新与“线程U中的c的读取同步。”与“同步”意味着之前在线程T中发生的所有事情当线程U随后读取c时,T更新c必须对线程U可见。

注意:“与...同步”的名称也是“在关系之前发生”,这有时会让新程序员感到困惑。当有人说“在一个线程c的更新发生在在另一个线程中读取c之前,”然后新手认为这意味着,“我的线程T将在我的线程U读取它之前更新c。“

“之前发生”并不意味着这一点。它只表示 IF 更新实际在读取之前发生,然后......

当线程T和线程U都没有任何控件时到达c时,这称为数据竞赛,如果程序的正确结果取决于哪个线程赢得比赛,那么你可以使用一些同步方法来确保正确的线程获胜。

答案 3 :(得分:-1)

  

incrementCounter(int i)方法由一个线程调用,之后   c变量的初始化,a,b和c的值是多少   主记忆和为什么?

我会将你的问题重新解释为线程A完成执行后线程B所看到的a,b和c的值。

根据JCIP的以下引用,主题B会看到ab的更新值以及c,但它附带一个谨慎 -

  

volatile变量的可见性效果超出了值的范围   易变量本身。当线程A写入易失性变量并且随后线程B读取相同的变量时,在写入易失性变量之前,A可见的所有变量的值在读取volatile变量后变为B可见。因此,从内存可见性的角度来看,编写volatile变量就像退出synchronized块一样,读取volatile变量就像进入synchronized块一样。

但随后它继续说,

  

但是,我们不建议过分依赖volatile变量来提高可见性;依赖于volatile变量来查看任意状态的代码比使用锁定的代码更脆弱,更难理解。

更新:根据Ralf的调查结果删除了有关易变变量原子性的陈述。在对变更历史做一些功课后,将重新审视该部分。