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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16248898/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 22:23:33  来源:igfitidea点击:

Memory Consistency - happens-before relationship in Java

javajava.util.concurrenthappens-before

提问by Prateek

While reading Java docs on Memory Consistency errors. I find points related to two actions that creates happen - before relationship:

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

  • When a statement invokes Thread.start(), every statement that has a happens-before relationship with that statement also has a happens-before relationship with every statement executed by the new thread. The effects of the code that led up to the creation of the new thread are visible to the new thread.

  • When a thread terminates and causes a Thread.join()in another thread to return, then all the statements executed by the terminated
    thread have a happens-before relationship with all the statements
    following the successful join. The effects of the code in the thread are now visible to the thread that performed the join.

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

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

I am not able to understand their meaning. It would be great if someone explain it with a simple example.

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

回答by Joachim Isaksson

Modern CPUs don't always write data to memory in the order it was updated, for example if you run the pseudo code (assuming variables are always stored to memory here for simplicity);

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

a = 1
b = a + 1

...the CPU may very well write bto memory before it writes ato memory. This isn't really a problem as long as you run things in a single thread, since the thread running the code above will never see the old value of either variable once the assignments have been made.

... CPU 可能会b在写入内存之前很好地写入a内存。只要您在单个线程中运行,这实际上就不是问题,因为一旦分配完成,运行上述代码的线程将永远不会看到任一变量的旧值。

Multi threading is another matter, you'd think the following code would let another thread pick up the value of your heavy computation;

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

a = heavy_computation()
b = DONE

...the other thread doing...

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

repeat while b != DONE
    nothing

result = a

The problem though is that the done flag may be set in memory before the result is stored to memory, so the other thread may pick up the value of memory address a before the computation result is written to memory.

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

The same problem would - if Thread.startand Thread.joindidn't have a "happens before" guarantee- give you problems with code like;

同样的问题会 -如果Thread.start并且Thread.join没有“之前发生”的保证- 会给您带来类似代码的问题;

a = 1
Thread.start newthread
...

newthread:
    do_computation(a)

...since amay not have a value stored to memory when the thread starts.

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

Since you almost always want the new thread to be able to use data you initialized before starting it, Thread.starthas a "happens before" guarantee, that is, data that has been updated before calling Thread.startis guaranteed to be available to the new thread. The same thing goes for Thread.joinwhere data written by the new thread is guaranteed to be visible to the thread that joins it after termination.

由于您几乎总是希望新线程能够使用您在启动之前初始化的数据,Thread.start因此具有“发生之前”的保证,即保证在调用之前已更新的数据Thread.start可用于新线程。同样的事情会Thread.join在那里被新的线程写入的数据被保证是可见的,在终止之后加入它的线程

It just makes threading much easier.

它只是使线程更容易。

回答by Evgeniy Dorofeev

Consider this:

考虑一下:

static int x = 0;

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

The main thread has changed field x. Java memory model does not guarantee that this change will be visible to other threads if they are not synchronized with the main thread. But thread twill see this change because the main thread called t.start()and JLS guarantees that calling t.start()makes the change to xvisible in t.run()so yis guaranteed to be assigned 1.

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

The same concerns Thread.join();

同样的顾虑 Thread.join();

回答by Eyal Schneider

Thread visibility problemsmay occur in a code that isn't properly synchronized according to the java memory model. Due to compiler & hardware optimizations, writes by one thread aren't always visible by reads of another thread. The Java Memory Model is a formal model that makes the rules of "properly synchronized" clear, so that programmers can avoid thread visibility problems.

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

Happens-beforeis a relation defined in that model, and it refers to specific executions. A write W that is proven to be happens-beforea read R is guaranteed to be visible by that read, assuming that there's no other interfering write (i.e. one with no happens-before relation with the read, or one happening between them according to that relation).

Happens-before是该模型中定义的关系,它指的是特定的执行。这被证明是一个写W¯¯之前发生的读R为保证通过读取可见,假设没有其他干扰写入(即一个没有与读取关系之前发生,或一个两者之间根据发生那种关系)。

The simplest kind of happens-before relation happens between actions in the same thread. A write W to V in thread P happens-before a read R of V in the same thread, assuming that W comes before R according to the program order.

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

The text you are referring to states that thread.start() and thread.join() also guarantee happens-before relationship. Any action that happens-before thread.start() also happens before any action within that thread. Similarly, actions within the thread happen before any actions that appear after thread.join().

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

What's the practical meaning of that? If for example you start a thread and wait for it to terminate in a non-safe manner (e.g. a sleep for a long time, or testing some non-synchronized flag), then when you'll try reading the data modifications done by the thread, you may see them partially, thus having the risk of data inconsistencies. The join() method acts as a barrier that guarantees that any piece of data published by the thread is visible completely and consistently by the other thread.

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

回答by Ken Block

According to oracle document, they define that The happens-before relationship is simply a guarantee that memory writesby one specific statement are visibleto another specific statement.

根据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] %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);
        }

    }
}

Output will be:

输出将是:

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

Have a look output, line [Thread Thread-0] start count: 1shows that all counter changes before invocation of Thread.start() are visible in Thread's body.

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

And line [Thread main] Finish count: 2indicates that all changes in Thread's body are visible to main thread that calls Thread.join().

并且行[Thread main] Finish count: 2表示调用 Thread.join() 的主线程可以看到 Thread 主体中的所有更改。

Hope it can help you clearly.

希望它可以帮助你清楚。