【发布时间】:2016-07-17 16:43:48
【问题描述】:
考虑以下从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 是否可以重新排序以使程序无法获得预期的输出(计数器 = 2)?如果不是,JLS 的哪一部分指定Thread.start 不能重新排序?
另一个问题:
如果join() 放在threadPrintMessage("Finish count: " + counter); 之后会发生什么? stop count: 1 会不会被打印出来?
【问题讨论】:
-
不管开始是否重新排序,输出不一定是 2,因为无法保证更新计数器的可见性。
-
@AndyTurner,你能回顾一下stackoverflow.com/questions/16248898/… 中的第二个答案吗?它指出
JLS guarantees that calling t.start() makes the change to x visible in t.run() so y is guaranteed to be assigned 1,所以是不是一个错误的说法? -
@chainro 这绝对是正确的。正如我所说,从上到下。在
Thread.start之前,您是单线程的,因此尊重自然顺序 -
@chainro 为没有更明确表示歉意:新线程对
counter的更新不保证对主线程可见。 -
@AndyTurner,我想知道 Thread.start() 是否具有与锁相似的语义?你知道,如果我们释放锁,所有加锁后的变量,即使是非易失性的,都可以被刷新。
标签: java multithreading concurrency atomic