【问题标题】:When Java refresh Thread Cache to actual copy当Java刷新线程缓存到实际副本时
【发布时间】:2014-03-27 15:01:48
【问题描述】:

看了几篇关于volatile Thread cache的文章,发现要么太简短,没有例子,初学者很难理解。

请帮助我理解下面的程序,

public class Test {
    int a = 0;
    public static void main(String[] args) {        
        final Test t = new Test();
        new Thread(new Runnable(){
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (Exception e) {}
                t.a = 10;
                System.out.println("now t.a == 10");
            }
        }).start();

        new Thread(new Runnable(){
            public void run() {
                while(t.a == 0) {}
                System.out.println("Loop done: " + t.a);
            }
        }).start();
    }

}

当我创建a 变量volatile 并运行我的程序时,它会在一段时间后停止,但是当我将volatile 删除为a 变量时,它会继续运行,并且我的程序没有停止。

我对 volatile 的了解是“当变量声明为 volatile 时,线程将直接读取/写入变量内存,而不是从本地线程缓存读取/写入。 如果未声明为 volatile,则可以看到实际值的更新延迟。"

另外,根据我对刷新缓存副本的理解,我认为程序会在一段时间后停止,但是为什么在上面的程序中它会继续运行而不是更新。

那么线程引用其本地缓存何时开始引用主副本或用主副本值刷新其值?

如果我的理解有误,请纠正我......

请用一些小代码sn-p或链接解释一下。

【问题讨论】:

    标签: java multithreading volatile


    【解决方案1】:

    如果变量未声明为 volatile,则无法预测它是从缓存中读取还是从主副本中读取。

    缓存的大小有限,变量可能由于各种原因从缓存中被驱逐,就像其他变量占用缓存一样。发生这种情况时,会读取主副本。

    【讨论】:

      【解决方案2】:

      这可能是也可能不是必然发生的事情,但volatile 也可以防止编译器进行某些重新排序。

      例如你的代码在这里

      while(t.a == 0) {}
      System.out.println("Loop done: " + t.a);
      

      可以重新排序

      if(t.a == 0){
         while(true){
         }
      }
      System.out.println("Loop done: " + t.a);
      

      这称为吊装,完全合法。将其声明为 volatile 将阻止这种排序。

      【讨论】:

      • 我同意提升,但我认为您的示例是错误的:在您的第二个代码中,System.out.print 每次迭代都会调用,这与第一个代码的效果不同。此外,重新排序和提升并不完全相同,请参阅stackoverflow.com/questions/11430095/…
      • 你说得对 system.out.println,我把它移到了下面
      • @PierreRust Hoisting 是一种代码运动,可以粗略地称为“重新排序”。
      【解决方案3】:

      当变量声明为 volatile 时,线程将直接读取/写入变量内存,而不是从本地线程缓存读取/写入。如果未声明为 volatile,则可以看到实际值的更新延迟。

      首先,上述陈述是错误的。在机器代码级别上发生了更多与任何“线程局部变量缓存”无关的现象。事实上,这个概念根本不适用。

      为了给你一些特定的关注,JIT 编译器将被允许转换你的代码

      while(t.a == 0) {}
      

      进入

      if (t.a == 0) while (true) {}
      

      只要t.a 不是volatile。 Java 内存模型允许将在数据竞争中访问的任何变量视为访问线程是存在的唯一线程。因为很明显这个线程没有修改t.a,它的值可以被认为是一个循环不变量并且不需要重复检查......永远。 p>

      【讨论】:

      • 实际上,如果我们按照标准,它根本不适用.. 那里根本没有提到“从内存中读取”我们这样的东西。一切都是为了保证订购!
      • 当然可以,但是人们仍然希望了解实际发生的事情以及 JMM 成为它的动机是什么。另一方面,OP 很可能需要 JMM 中的入门 :-)
      • 哦,我确定你知道这一切。你正确地驳斥了关于整个缓存的说法,我只是觉得非常明确地表达它是非常重要的。这是一个很好的谎言,很容易理解,但最终它并不是真正起作用的。如果您要进行这样的低级并发编程,我觉得最后这些小细节真的很重要:-)
      • 确实,这个答案更多的是关于低级别而不是关于JMM。要充分理解,应该熟悉这两个级别,但如果必须只选择一个,那肯定是 JMM。但是,也必须满足对下面到底发生了什么的想法的诱惑:)
      【解决方案4】:

      标记变量volatile 是为了确保对它的更改跨线程可见。

      在您的代码中,第一个线程更改 a 的值,其他线程看到此更改并跳出循环。

      关于 volatile 变量的重要一点是值可以在上次访问和当前访问之间改变,即使编译器知道它的值没有被改变。 (正如@Marko Topolnik 所说) 这会阻止 JIT 编译器进行像

      这样的优化
      while(t.a == 0) {}
      

      进入

      if (t.a == 0) while (true) {}
      

      知道a不能改变。

      这个演讲很好地解释了这些事情。 Jeremy Menson's talk on Java Memory Model@Google

      【讨论】:

        猜你喜欢
        • 2011-06-28
        • 1970-01-01
        • 1970-01-01
        • 2014-04-10
        • 2011-11-16
        • 1970-01-01
        • 1970-01-01
        • 2020-03-06
        相关资源
        最近更新 更多