Java 内存模型中的先行规则
Happens-before rules in Java Memory Model
我目前正在为并发编程考试学习,不明白为什么这个程序的输出是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;
}
没有同步,没有 volatile
字段,没有锁定,没有原子字段。代码可以按任何顺序执行。
是的,t.start()
会在 x = y + 1
之前执行。但是启动线程并不意味着线程体在x = y + 1
之前执行。它可以 运行 之前、之后或与 main()
的其余部分交错。
t.start
在x = y + 1
之前,不保证run()
方法中的每一行代码都会在x = y + 1
.
之前执行
事实上,由于竞争条件,没有同步打印的结果是不确定的,可能是1
或43
。
根据JMM:
A call to start() on a thread happens-before any actions in the
started thread.
和
If x and y are actions of the same thread and x comes before y in
program order, then hb(x, y).
程序顺序的定义是这样的:
Among all the inter-thread actions performed by each thread t, the
program order of t is a total order that reflects the order in which
these actions would be performed according to the intra-thread
semantics of t.
线程间语义是 JMM 中定义明确的概念。这意味着每个线程执行的程序中的指令顺序必须保留在程序文本中所写的顺序。
将所有这些应用到您的案例中:
t.start();
hb x = y + 1;
//程序顺序
t.start();
hb y = x;
// happens-before 规则指定 here
如果没有额外的同步,我们无法说明 x = y + 1;
和 y = x;
之间的关系(从 JMM 的角度来看)。
如果您正在尝试回答问题 "What can happen at runtime in my case?"。可能会发生很多事情...看看这个 。运行时可以执行与 JMM 一致的重要优化。
无论如何,如果您对内部结构感兴趣,可以看看这个 article(可以避免内存障碍)。正如您在生成的程序集中看到的,执行易失性读取时没有应用内存屏障。我的意思是运行时无论如何都可以优化...只要保留 JMM 规则...
If I understand the program order rule (each action in a thread
happens-before every action in that thread that comes later in the
program order)
不,不是。
在这里你没有一个线程而是两个线程:
主线程和由主线程创建和启动的线程:
Thread t = new Thread() {...};
t.start();
并且 JVM 的活动线程在设计上是并发的。
If the two statements had been executed in the same thread, it would
be safe to assume that the value printed out would be "1". But if the
two statements are executed in separate threads, the value printed out
might well be "0", because there's no guarantee that thread A's change
to counter will be visible to thread B — unless the programmer has
established a happens-before relationship between these two
statements.
仅当所有语句都由同一个线程执行或您显式创建先行关系时才会发生先行关系。
摘自 Memory Consistency Errors tutorial :
There are several actions that create happens-before relationships.
One of them is synchronization, as we will see in the following
sections.
如上所述,语句由两个线程执行。
而且您没有显式同步语句。
所以你在线程之间有一个竞争条件,作为一般规则,执行顺序应该被认为是不可预测的。
现在在实践中,对于这么短的语句:x = y + 1
:
t.start();
x = y + 1;
加操作的赋值时间太短,很可能发生在t
引用的线程有效运行.
[=之前19=]
此外,现代 CPUs 有多个核心。
所以如果CPU有线程可用,主线程不会暂停让新线程运行ning。
这两个线程将在 "same time".
处执行
但是由于 x = y + 1;
作为启动和 运行 线程执行起来要快得多,所以第一个语句只能在第二个语句之前完成。
我想补充一点,main 方法中的代码在 Main 线程中运行,并且 Thread t 在您的示例中不会阻止 Main 的执行。这就是为什么 x = y + 1
行可能比线程 t 的主体执行得更快(正如 @davidxxx 已经指出的那样)。
如果在 t.start():
之后添加 t.join()
,您可以观察到不同的行为
...
t.start();
t.join();
在这种情况下,主线程将等待线程 t 完成,输出将为 1。
我目前正在为并发编程考试学习,不明白为什么这个程序的输出是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;
}
没有同步,没有 volatile
字段,没有锁定,没有原子字段。代码可以按任何顺序执行。
是的,t.start()
会在 x = y + 1
之前执行。但是启动线程并不意味着线程体在x = y + 1
之前执行。它可以 运行 之前、之后或与 main()
的其余部分交错。
t.start
在x = y + 1
之前,不保证run()
方法中的每一行代码都会在x = y + 1
.
事实上,由于竞争条件,没有同步打印的结果是不确定的,可能是1
或43
。
根据JMM:
A call to start() on a thread happens-before any actions in the started thread.
和
If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
程序顺序的定义是这样的:
Among all the inter-thread actions performed by each thread t, the program order of t is a total order that reflects the order in which these actions would be performed according to the intra-thread semantics of t.
线程间语义是 JMM 中定义明确的概念。这意味着每个线程执行的程序中的指令顺序必须保留在程序文本中所写的顺序。
将所有这些应用到您的案例中:
t.start();
hb x = y + 1;
//程序顺序
t.start();
hb y = x;
// happens-before 规则指定 here
如果没有额外的同步,我们无法说明 x = y + 1;
和 y = x;
之间的关系(从 JMM 的角度来看)。
如果您正在尝试回答问题 "What can happen at runtime in my case?"。可能会发生很多事情...看看这个
无论如何,如果您对内部结构感兴趣,可以看看这个 article(可以避免内存障碍)。正如您在生成的程序集中看到的,执行易失性读取时没有应用内存屏障。我的意思是运行时无论如何都可以优化...只要保留 JMM 规则...
If I understand the program order rule (each action in a thread happens-before every action in that thread that comes later in the program order)
不,不是。
在这里你没有一个线程而是两个线程:
主线程和由主线程创建和启动的线程:
Thread t = new Thread() {...};
t.start();
并且 JVM 的活动线程在设计上是并发的。
If the two statements had been executed in the same thread, it would be safe to assume that the value printed out would be "1". But if the two statements are executed in separate threads, the value printed out might well be "0", because there's no guarantee that thread A's change to counter will be visible to thread B — unless the programmer has established a happens-before relationship between these two statements.
仅当所有语句都由同一个线程执行或您显式创建先行关系时才会发生先行关系。
摘自 Memory Consistency Errors tutorial :
There are several actions that create happens-before relationships. One of them is synchronization, as we will see in the following sections.
如上所述,语句由两个线程执行。
而且您没有显式同步语句。
所以你在线程之间有一个竞争条件,作为一般规则,执行顺序应该被认为是不可预测的。
现在在实践中,对于这么短的语句:x = y + 1
:
t.start();
x = y + 1;
加操作的赋值时间太短,很可能发生在t
引用的线程有效运行.
[=之前19=]
此外,现代 CPUs 有多个核心。
所以如果CPU有线程可用,主线程不会暂停让新线程运行ning。
这两个线程将在 "same time".
处执行
但是由于 x = y + 1;
作为启动和 运行 线程执行起来要快得多,所以第一个语句只能在第二个语句之前完成。
我想补充一点,main 方法中的代码在 Main 线程中运行,并且 Thread t 在您的示例中不会阻止 Main 的执行。这就是为什么 x = y + 1
行可能比线程 t 的主体执行得更快(正如 @davidxxx 已经指出的那样)。
如果在 t.start():
t.join()
,您可以观察到不同的行为
...
t.start();
t.join();
在这种情况下,主线程将等待线程 t 完成,输出将为 1。