在阅读有关内存一致性错误的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.start
和Thread.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()的主线程都是可见的。