6

我目前正在学习并发编程考试,不明白为什么这个程序的输出是43。为什么x = y + 1之前执行t.start()?我还应该解释我使用了哪些发生之前的规则。

如果我理解程序顺序规则(线程中的每个动作发生 - 在该线程中稍后在程序顺序中出现的每个动作之前)t.start()必须在之前执行,x = y + 1以便线程 t 复制变量x,该变量将为 1。

public class HappensBefore {

static int x = 0;
static int y = 42;

public static void main(String[] args) {
    x = 1;
    Thread t = new Thread() {
        public void run() {
            y = x;
            System.out.println(y);
        };
    };
    t.start();
    x = y + 1;
}
4

5 回答 5

9

没有同步,没有volatile字段,没有锁​​定,没有原子字段。代码可以按任何顺序执行。

是的,t.start()会在之前执行x = y + 1。但是启动线程并不意味着线程体在之前执行x = y + 1。它可以在main().

于 2018-01-27T14:50:49.190 回答
5

根据JMM

线程上的 start() 调用发生在已启动线程中的任何操作之前。

如果 x 和 y 是同一线程的操作,并且 x 在程序顺序中位于 y 之前,则为 hb(x, y)。

程序顺序的定义是这样的:

在每个线程 t 执行的所有线程间动作中,t 的程序顺序是一个总顺序,它反映了根据 t 的线程内语义执行这些动作的顺序。

线程间语义是 JMM 中明确定义的概念。这意味着每个线程执行的程序中的指令顺序必须保留在程序文本中写入的顺序。

将所有这些应用于您的案例:

t.start(); hb x = y + 1; //程序顺序

t.start(); hb // 此处y = x; 指定的发生前规则

如果没有额外的同步,我们就无法说出 彼此之间的关系x = y + 1;和关系(从 JMM 的角度来看)。y = x;

如果您试图回答“在我的情况下运行时会发生什么?”这个问题。可能发生很多事情......看看这个asnwer。运行时可以执行与 JMM 一致的重要优化。

无论如何,如果您对内部结构感兴趣,可以看看这篇文章(可以避免内存障碍)。正如您在生成的程序集中看到的那样,执行易失性读取时没有应用内存屏障。我的意思是运行时无论如何都可以优化......只要保留JMM规则......

于 2018-01-27T15:04:49.157 回答
4

如果我了解程序顺序规则(线程中的每个操作都发生在该线程中稍后在程序顺序中的每个操作之前)

不,它没有。
这里不是一个线程,而是两个线程:主线程和由主线程创建和启动的线程:

Thread t = new Thread() {...};
t.start();

JVM 的活线程在设计上是并发的。

如果这两个语句在同一个线程中执行,则可以安全地假设打印输出的值为“1”。但是如果这两个语句在不同的线程中执行,打印出来的值很可能是“0”,因为不能保证线程 A 对计数器的更改对线程 B 是可见的——除非程序员已经在这两种说法。

仅当所有语句都由同一线程执行或显式创建发生前关系时,才会发生前发生关系。

摘自内存一致性错误教程

有几个动作可以创建先发生关系。其中之一是同步,我们将在以下部分中看到。

如上所述,语句由两个线程执行。
而且您没有显式同步语句。
所以你在线程之间有一个竞争条件,作为一般规则,执行顺序应该被认为是不可预测的。现在在实践中,对于这样一个简短的声明: x = y + 1:

t.start(); 
x = y + 1;

加法操作的赋值很短,因为它很可能在被引用的线程t被有效运行之前发生。

此外,现代 CPU 具有多个内核。
所以如果 CPU 有线程可用,主线程不会被暂停让新线程运行。
这两个线程将因此“同时”执行。
但是由于 x = y + 1;启动和运行线程的执行速度要快得多,所以第一条语句只能在第二条语句之前完成。

于 2018-01-27T15:29:29.193 回答
2

t.start是before x = y + 1,它不保证run()method中的每一行代码都会在before执行x = y + 1

事实上,由于竞态条件,打印结果在没有同步的情况下是不确定的,它可能是143

于 2018-01-27T14:53:12.197 回答
1

我想补充一点,main 方法中的代码在 Main 线程中运行,并且Thread t在您的示例中不会阻止 Main 的执行。这就是为什么该行x = y + 1可能比线程t的主体执行得更快(正如@davidxxx已经指出的那样)。

如果在t.join()之后添加,您可以观察到不同的行为t.start():

...
t.start();
t.join();

在这种情况下,主线程将等待线程t完成,输出将为 1。

于 2018-02-03T13:01:57.213 回答