考虑从Memory Consistency - happens-before relationship in Java借来的以下场景:
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);
}
}
我知道JLS中有一条规则保证Thread.start
在启动线程中的所有操作之前发生。
当一个语句调用Thread.start时,每个语句都有一个 发生 - 在与该陈述的关系也有一个 发生在与新执行的每个语句的关系之前 线。导致创建新代码的代码的影响 线程对新线程可见。
但它没有声称Thread.start
之前的陈述与之前发生过关系。
所以我想知道可以重新排序Thread.start
以便程序无法获得预期的输出(counter = 2)?如果不是,JLS的哪一部分指定Thread.start
无法重新排序?
另一个问题:
如果在join()
之后放置threadPrintMessage("Finish count: " + counter);
会怎样?是否可能打印stop count: 1
?
答案 0 :(得分:0)
由于JLS§17.4.5 Thread.start
调用之前的操作与新线程的开始之间存在排序关系:
- 如果 x 和 y 是同一个帖子的操作, x 按照程序顺序出现在 y 之前,然后 hb(x,y)。
- ...
- 如果 hb(x,y)和 hb(y,z),则 hb(x,z)。
以及稍后的同一部分,已在您的问题保证中提及:
- 线程上的
start()
调用发生在已启动线程中的任何操作之前。
由于发生在关系之前的传递性质,在调用start()
之前,主线程的操作之间存在发生之前关系已启动线程中的操作。类似地,启动线程的操作与主线程join
调用的成功返回之间存在发生之前关系。
换句话说,只要您没有遇到InterruptedException
,就会对订单进行正确排序,打印结果为2
。然而,这并不意味着在JLS§17.4.5继续下去时,行为永远不会重新排序:
应该注意的是,在两个动作之间存在发生之前的关系并不一定意味着它们必须在实现中以该顺序发生。如果重新排序产生的结果与合法执行一致,则不是非法的。
换句话说,发生之前关系是一个高级概念,它允许您确定程序执行的合法结果。如果像在这个例子中那样,在程序结束时2
中counter
是唯一的合法结果(如上所述,假设没有发生中断),JVM可能会尽可能多地安排代码按照自己的意愿,只要该计划能够产生最终在2
中counter
的{{1}}的法律结果。
你应该停止打扰可能重新排序的内容。这是一个不重要的实施细节。如果没有正确的发生在之前的关系,您可能会遇到无序出现的更新,但也可能会错过更新或创建不一致的状态,因此关注一个实现细节是没有意义的可能会导致破坏程序出现意外的副作用。相反,要关注制作正确程序的内容。