2013-04-27 73 views
31

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

  • 当语句调用Thread.start(),每一个具有 声明之前发生这种说法的关系也有一个 之前发生与每一个关系语句由新的 线程执行。新线程可以看到导致创建新线程的代码的影响。

  • 当一个线程终止,并导致在另一个线程 一个Thread.join()返回,那么所有被终止
    线程执行的语句有之前发生的所有成功加入语句
    以下关系。代码对线程 的影响现在对执行连接的线程可见。

我无法理解它们的含义。如果有人用一个简单的例子来解释它,那将会很棒。

+5

'发生之前关系'意味着这些语句集合保证在另一组语句之前执行。因此,在第一种情况下,导致启动新线程的语句与新启动的线程将执行的语句之间具有发生之前的关系。这些语句所做的任何更改都将对线程执行的语句可见。 – 2013-04-27 06:23:11

+1

我觉得这个网页很有用:http://preshing.com/20130702/the-happens-before-relation/它给出了A和B之间“发生之前”关系与B之前实际发生的A不同的例子。 – 2015-02-11 17:40:19

回答

26

现代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,其中由新线程写入的数据保证对终止后加入它的线程可见。

它只是使线程更容易。

7

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

发生在之前的是在该模型中定义的关系,它指的是特定的执行。被证明为的写入W发生在之前发生,读取R被保证可以被该读取看到,假设没有其他干扰写入(即没有发生 - 在与读取之间的关系之前或者在它们之间发生之一根据那个关系)。

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

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

那是什么意思?例如,如果您启动一个线程并等待它以非安全方式终止(例如,长时间休眠或测试某些非同步标志),那么当您尝试读取数据修改线程,你可能会看到它们的一部分,从而存在数据不一致的风险。 join()方法充当了一个屏障,可以保证线程发布的任何数据片段都可以被其他线程完全一致地看到。

18

考虑一下:

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将看到此更改,因为名为t.start()的主线程和JLS保证呼叫t.start()使更改为在t.run()中可见,因此y保证被分配为1

了同样的担忧Thread.join();

+0

嗯,我同意,稍微改变了答案,以避免critisizm,现在看看它 – 2013-04-27 07:03:33

1

据Oracle文档,它们定义之前发生关系是一个简单的保证内存由一个特定的声明写道:可见到另一个具体声明。

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 

看一看输出,线[线程的线程0]开始计数:1表明Thread.start的调用之前(所有计数器的变化)是在Thread的主体中可见。

和行[Thread main]完成计数:2指示调用Thread.join()的主线程可以看到Thread的正文中的所有更改。

希望它能帮助你清楚。

相关问题