【问题标题】:Memory Consistency - happens-before relationship in Java [duplicate]内存一致性-Java中的发生之前的关系[重复]
【发布时间】:2013-04-21 08:43:08
【问题描述】:

在阅读有关内存一致性错误的 Java 文档时。我发现与创建发生的两个动作相关的点 - 在关系之前:

  • 当一个语句调用 Thread.start() 时,每个语句都有一个 与该语句的发生之前的关系也有 与新执行的每个语句的发生前关系 线。导致创建的代码的影响 新线程对新线程可见。

  • 当一个线程终止并在另一个线程中导致Thread.join() 要返回,则终止执行的所有语句
    线程与所有语句都有发生前的关系
    加入成功后。代码在线程中的效果 现在对执行连接的线程可见。

我无法理解它们的含义。如果有人用一个简单的例子来解释,那就太好了。

【问题讨论】:

  • 'happens-before 关系'意味着保证这些语句集在另一组语句之前执行。因此,在第一种情况下.. 导致新线程启动的语句与将由新启动的线程执行的语句具有发生前的关系。这些语句所做的任何更改都将对线程执行的语句可见。
  • 我觉得这个页面很有帮助:preshing.com/20130702/the-happens-before-relation 它举例说明了 A 和 B 之间的“之前发生”的关系与实际发生在 B 之前的 A 的不同。

标签: java java.util.concurrent happens-before


【解决方案1】:

现代 CPU 并不总是按照更新的顺序将数据写入内存,例如,如果您运行伪代码(为简单起见,假设变量总是存储在内存中);

a = 1
b = a + 1

...CPU 很可能先将b 写入内存,然后再将a 写入内存。只要您在单个线程中运行,这并不是一个真正的问题,因为一旦进行了分配,运行上述代码的线程将永远不会看到任何一个变量的旧值。

多线程是另一回事,你会认为下面的代码会让另一个线程获取你繁重计算的价值;

a = heavy_computation()
b = DONE

...另一个线程正在做...

repeat while b != DONE
    nothing

result = a

但问题是完成标志可能会在结果存储到内存之前在内存中设置,因此 其他线程可能会在计算结果写入内存之前获取内存地址 a 的值。

同样的问题会 - 如果 Thread.startThread.join 没有“发生在之前”的保证 - 会给你这样的代码问题;

a = 1
Thread.start newthread
...

newthread:
    do_computation(a)

...因为a 在线程启动时可能没有值存储到内存中。

由于您几乎总是希望新线程能够在启动它之前使用您初始化的数据,Thread.start 有一个“发生在之前”的保证,即 在调用 Thread.start 之前已经更新的数据保证对新线程可用Thread.join 也是如此,新线程写入的数据保证在终止后对加入它的线程可见

它只是让线程更容易。

【讨论】:

    【解决方案2】:

    考虑一下:

    static int x = 0;
    
    public static void main(String[] args) {
        x = 1;
        Thread t = new Thread() {
            public void run() {
                int y = x;
            };
        };
        t.start();
    }
    

    主线程已更改字段x。如果其他线程与主线程不同步,Java 内存模型不保证此更改对其他线程可见。但是线程t 会看到这种变化,因为名为@9​​87654324@ 的主线程和JLS 保证调用t.start() 会使x 的变化在t.run() 中可见,因此保证y 被分配1

    同样的担忧Thread.join();

    【讨论】:

    • 嗯,我同意,为了避免批评,稍微改变了答案,现在看看吧
    • 线程t除了主线程之外还有单独的执行路径。而且由于在同一个对象上没有同步,因此无法保证。线程 t 的执行确实是由主线程发起的。但是仅仅因为主线程 c/d 内部线程,它并不能保证可见性的变化。您能否在 JLS 中提供参考 - JLS 保证调用 t.start() 会使对 x 的更改在 t.run() 中可见
    • 我在 JLS 中找到了重点。它也在 JCIP 第 16 章中提到。Java 内存模型 -->> 发生之前的规则是:线程启动规则。线程上的 Thread.start 调用发生在启动线程中的每个操作之前。你说的对。我投了赞成票。
    【解决方案3】:

    线程可见性问题可能发生在没有根据 java 内存模型正确同步的代码中。由于编译器和硬件优化,一个线程的写入并不总是对另一个线程的读取可见。 Java Memory Model 是一种形式化的模型,它使“正确同步”的规则变得清晰,让程序员可以避免线程可见性问题。

    Happens-before 是该模型中定义的关系,它指的是特定的执行。假设没有其他干扰写入(即与读取没有发生之前的关系,或者根据这种关系在它们之间发生一种情况)。

    最简单的happens-before关系发生在同一个线程中的动作之间。在线程 P 中将 W 写入 V 发生在同一线程中读取 V 之前,假设 W 根据程序顺序在 R 之前。

    您所指的文本指出 thread.start() 和 thread.join() 也保证发生前的关系。任何在 thread.start() 之前发生的动作也发生在该线程内的任何动作之前。同样,线程内的动作发生在出现在 thread.join() 之后的任何动作之前。

    这有什么实际意义?例如,如果您启动一个线程并等待它以非安全方式终止(例如长时间休眠,或测试一些非同步标志),那么当您尝试读取由线程,您可能会部分看到它们,因此存在数据不一致的风险。 join() 方法充当屏障,保证线程发布的任何数据片段对其他线程完全一致地可见。

    【讨论】:

      【解决方案4】:

      根据oracle文档,他们定义happens-before关系只是保证一个特定语句的内存写入对另一个特定语句可见。 p>

      package happen.before;
      
      public class HappenBeforeRelationship {
      
      
          private static int counter = 0;
      
          private static void threadPrintMessage(String msg){
              System.out.printf("[Thread %s] %s\n", Thread.currentThread().getName(), msg);
          }
      
          public static void main(String[] args) {
      
              threadPrintMessage("Increase counter: " + ++counter);
              Thread t = new Thread(new CounterRunnable());
              t.start();
              try {
                  t.join();
              } catch (InterruptedException e) {
                  threadPrintMessage("Counter is interrupted");
              }
              threadPrintMessage("Finish count: " + counter);
          }
      
          private static class CounterRunnable implements Runnable {
      
              @Override
              public void run() {
                  threadPrintMessage("start count: " + counter);
                  counter++;
                  threadPrintMessage("stop count: " + counter);
              }
      
          }
      }
      

      输出将是:

      [Thread main] Increase counter: 1
      [Thread Thread-0] start count: 1
      [Thread Thread-0] stop count: 2
      [Thread main] Finish count: 2
      

      看看输出,行 [Thread Thread-0] start count: 1 表明调用 Thread.start() 之前的所有计数器更改在 Thread 的主体中都是可见的。

      而行[Thread main] Finish count: 2 表示调用Thread.join()的主线程可以看到线程主体的所有变化。

      希望对你有帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-02-06
        • 2013-04-19
        • 1970-01-01
        • 2020-03-18
        • 2018-07-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多