Java中的内存一致性-先发生后发生的关系



在阅读有关内存一致性错误的Java文档时。我发现与两种行为相关的点可以创造"事前发生"的关系:

  • 当一个语句调用Thread.start()时,每个具有Happens-before关系与该语句也有函数所执行的每个语句的Happens-before关系线程。代码的影响导致了创建

  • 当一个线程终止并在另一个线程中引起Thread.join()时若要返回,则终止
    所执行的所有语句线程与所有语句都有happens-before关系
    成功加入后。线程中代码的效果

我不能理解他们的意思。如果有人用一个简单的例子来解释就太好了。

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

a = 1
b = a + 1

…CPU很可能在将a写入内存之前将b写入内存。只要在单个线程中运行,这就不是真正的问题,因为一旦赋值完成,运行上述代码的线程将永远不会看到这两个变量的旧值。

多线程是另一回事,您可能认为下面的代码会让另一个线程拾取您繁重计算的值;

a = heavy_computation()
b = DONE

…另一个线程正在做…

repeat while b != DONE
    nothing
result = a
问题是done标志可能在结果被存储到内存之前被设置在内存中,所以其他线程可能在计算结果被写入内存之前拾取内存地址a的值

同样的问题将- 如果Thread.startThread.join没有"之前发生"保证 -给你类似的代码问题;

a = 1
Thread.start newthread
...
newthread:
    do_computation(a)

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

由于您几乎总是希望新线程能够使用在启动之前初始化的数据,因此Thread.start具有"happens before"保证,即在调用Thread.start之前更新的数据保证对新线程可用。Thread.join也是如此,新线程写入的数据保证在终止后对加入它的线程可见。

它只是使线程化更容易。

考虑一下:

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()使x的变化在t.run()中可见,因此保证y被分配给1

同样的问题Thread.join();

线程可见性问题可能发生在没有根据java内存模型正确同步的代码中。由于编译器&硬件优化,一个线程的写操作并不总是能被另一个线程的读操作看到。Java内存模型是一个正式的模型,它使"适当同步"的规则清晰,因此程序员可以避免线程可见性问题。

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

最简单的"先发生"关系发生在同一个线程中的操作之间。假设根据程序顺序,W在R之前出现,在线程P中写W到V发生在同一线程中读R到V之前。

您引用的文本声明thread.start()和thread.join()也保证happens-before关系。在thread.start()之前发生的任何操作也发生在该线程内的任何操作之前。类似地,线程中的操作发生在thread.join()之后出现的任何操作之前。

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

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

package happen.before;
public class HappenBeforeRelationship {

    private static int counter = 0;
    private static void threadPrintMessage(String msg){
        System.out.printf("[Thread %s] %sn", 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()的主线程都是可见的。

最新更新