在阅读有关内存一致性错误的Java文档时。我找到了与创造的两个动作相关的点 - 在关系之前:
当一个语句调用Thread.start()
时,每个语句都有一个。{
发生 - 在与该陈述的关系也有一个
发生在与新执行的每个语句的关系之前
线。导致创建的代码的影响
新线程对新线程可见。
当一个线程终止并在另一个线程中导致Thread.join()
时
返回,然后由终止执行的所有语句
线程与所有语句的发生关系
成功加入后。线程中代码的影响
现在可以看到执行连接的线程。
我无法理解他们的意思。如果有人用一个简单的例子解释它会很棒。
答案 0 :(得分:31)
现代CPU并不总是按照更新的顺序将数据写入内存,例如,如果运行伪代码(假设为了简单起见,变量总是存储在内存中);
a = 1
b = a + 1
...在将b
写入内存之前,CPU可能会很好地将a
写入内存。只要你在一个线程中运行东西,这不是一个真正的问题,因为运行上面代码的线程在完成赋值后将永远不会看到任一变量的旧值。
多线程是另一回事,您认为以下代码会让另一个线程获取繁重计算的值;
a = heavy_computation()
b = DONE
......另一个线程正在......
repeat while b != DONE
nothing
result = a
问题是在结果存储到内存之前可以在内存中设置完成标志,因此另一个线程可以在将计算结果写入内存之前获取内存地址a的值。 / em>的
同样的问题 - 如果Thread.start
和Thread.join
没有“发生之前”保证 - 给你代码问题,如<; p>
a = 1
Thread.start newthread
...
newthread:
do_computation(a)
...因为a
在线程启动时可能没有存储到内存中的值。
由于您几乎总是希望新线程能够使用您在启动之前初始化的数据,因此Thread.start
具有“之前发生”保证,即在调用之前已更新的数据{ {1}}保证可用于新线程。 Thread.start
也是如此,其中新线程写入的数据保证在终止后加入它的线程可见。
它使线程更容易。
答案 1 :(得分:21)
考虑一下:
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}}被分配1
。
同样关注Thread.join();
答案 2 :(得分:8)
线程可见性问题。由于编译器和硬件优化,一个线程的写入并不总是通过读取另一个线程可见。 Java Memory Model是一个正式的模型,它使“正确同步”的规则变得清晰,这样程序员就可以避免线程可见性问题。
Happens-before 是该模型中定义的关系,它指的是特定的执行。一个被证明是的写入W发生在之前,读取R保证在读取时可见,假设没有其他干扰写入(即没有发生与读取有关的事件,或根据这种关系发生在他们之间)。
在同一个线程中的动作之间发生最简单的事件。线程P中的写入W到V发生在同一线程中的V读取R之前,假设W根据程序顺序出现在R之前。
您所指的文本指出thread.start()和thread.join()也保证在之前发生关系。在thread.start()之前发生的任何操作也发生在该线程中的任何操作之前。类似地,线程内的操作发生在thread.join()之后出现的任何操作之前。
这有什么实际意义?例如,如果您启动一个线程并等待它以非安全方式终止(例如长时间休眠,或者测试一些非同步标志),那么当您尝试读取由线程,您可能会部分地看到它们,因此存在数据不一致的风险。 join()方法充当一个屏障,保证线程发布的任何数据片段都是由另一个线程完全可见的。
答案 3 :(得分:1)
根据oracle文档,他们定义了before-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);
}
}
}
输出将是:
[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]开始计数:1 表示调用Thread.start()之前所有计数器的变化都在Thread的主体中可见。
&line [Thread main] Finish count:2 表示Thread的正文中的所有更改都对调用Thread.join()的主线程可见。
希望它可以帮助你清楚。