java 易失性访问不能重新排序是真的吗?
Is it true that java volatile accesses cannot be reordered?
备注
通过说内存访问可以(或不能)重新排序,我的意思是它可以
在发出字节码 byte 时由编译器重新排序,或在发出时由 JIT 重新排序
机器代码或 CPU 在乱序执行时(最终需要障碍来防止这种情况)相对于任何其他内存访问。
如果经常阅读,由于 Happens-Before 关系 (HBR),无法重新排序对 volatile
变量的访问。
我发现在每两个连续的(按程序顺序)动作之间存在一个 HBR
一个给定的线程,但它们可以重新排序。
也是一个易失性访问 HB,只能访问相同的 variable/field。
我认为 volatile
不可重新订购的是这个
A write to a volatile field (§8.3.1.4) happens-before every subsequent read [of any thread]
of that field.
如果有其他线程变量的重新排序将变得可见
简单的例子
volatile int a, b;
Thread 1 Thread 2
a = 1; while (b != 2);
b = 2; print(a); //a must be 1
所以不是 HBR 本身阻止了排序,而是 volatile
扩展了与其他线程的这种关系,其他线程的存在是阻止重新排序的因素.
如果编译器可以证明 volatile 变量的重新排序不会改变
程序语义它可以重新排序即使有 HBR.
如果一个 volatile 变量除了它的访问之外永远不会被其他线程访问
可以重新排序
volatile int a, b, c;
Thread 1 Thread 2
a = 1; while (b != 2);
b = 2; print(a); //a must be 1
c = 3; //c never accessed by Thread 2
我认为 c=3
可以在 a=1
之前重新排序,这来自规范
确认这一点
It should be noted that the presence of a happens-before relationship between
two actions does not necessarily imply that they have to take place in that order
in an implementation. If the reordering produces results consistent with a legal
execution, it is not illegal.
所以我制作了这些简单的 java 程序
public class vtest1 {
public static volatile int DO_ACTION, CHOOSE_ACTION;
public static void main(String[] args) {
CHOOSE_ACTION = 34;
DO_ACTION = 1;
}
}
public class vtest2 {
public static volatile int DO_ACTION, CHOOSE_ACTION;
public static void main(String[] args) {
(new Thread(){
public void run() {
while (DO_ACTION != 1);
System.out.println(CHOOSE_ACTION);
}
}).start();
CHOOSE_ACTION = 34;
DO_ACTION = 1;
}
}
在这两种情况下,两个字段都标记为 volatile
并使用 putstatic
访问。
由于这些是 JIT 拥有的所有信息1,机器代码将是相同的,
因此 vtest1
访问不会被优化2.
我的问题
易失性访问是否真的从未按规范重新排序,或者它们可能是3,但实际上从未这样做过?
如果永远无法对易失性访问进行重新排序,那么规范的哪些部分是这样说的?这是否意味着 CPUs?
按程序顺序执行和查看所有易失性访问
1或者JIT可以知道一个字段永远不会被其他线程访问?如果是,如何?
2例如内存障碍。
3例如,如果不涉及其他线程。
JLS 所说的(来自 JLS-8.3.1.4. volatile Fields)部分是
The Java programming language provides a second mechanism, volatile
fields, that is more convenient than locking for some purposes.
A field may be declared volatile
, in which case the Java Memory Model ensures that all threads see a consistent value for the variable (§17.4).
这意味着访问可能被重新排序,但任何重新排序的结果必须最终一致(当被另一个线程访问时)与原始订单。单线程应用程序中的字段 不需要 锁定(来自 volatile
或 synchronization
)。
我将使用来自 JLS §17.4.5 的符号。
在你的第二个例子中,(如果你能原谅我的松散符号)你有
线程 1 排序:
hb(a = 1
, b = 2
)
hb(b = 2
, c = 3
)
不稳定的保证:
hb(b = 2
, b != 2
)
hb(a = 1
, 为 print
访问 a
)
线程 2 排序:
hb(while(b != 2);
, print(a)
)
我们有(强调我的)
More specifically, if two actions share a happens-before relationship,
they do not necessarily have to appear to have happened in that order
to any code with which they do not share a happens-before
relationship. Writes in one thread that are in a data race with reads
in another thread may, for example, appear to occur out of order to
those reads.
c=3
和线程 2 之间没有发生之前的关系。实现可以自由地重新排序 c=3
到它的核心内容。
The memory model describes possible behaviors of a program. An implementation is free to produce any code it likes, as long as all resulting executions of a program produce a result that can be predicted by the memory model.
This provides a great deal of freedom for the implementor to perform a myriad of code transformations, including the reordering of actions and removal of unnecessary synchronization.
Java 内存模型为正确同步的程序提供顺序一致性 (SC)。简单来说,SC 意味着如果某个程序的所有可能执行都可以通过不同的执行来解释,其中所有内存操作都按某种顺序执行,并且该顺序与每个线程的程序顺序 (PO) 一致,那么该程序与这些顺序执行一致;所以它是顺序一致的(因此得名)。
这实际上意味着 JIT/CPU/memory 子系统可以根据需要重新排序易失性写入和读取,只要存在也可以解释实际执行结果的顺序执行。所以实际的执行并不那么重要。
如果我们看下面的例子:
volatile int a, b, c;
Thread 1 Thread 2
a = 1; while (c != 1);
b = 1; print(b);
c = 1;
在a=1
和b=2
之间有一个happens before关系(PO),在c=2
和c=3
之间有一个happens before关系(PO)和一个happens在关系 c=1
和 c!=0
之前(可变变量规则)和在 c!=0
和 print(b)
(PO) 之间的关系之前发生。
由于happens before关系是传递的,所以a=1
和print(b)
之间存在happens before关系。所以从这个意义上说,它不能重新排序。但是,没有人证明发生了重新排序,所以仍然可以重新排序。
备注
通过说内存访问可以(或不能)重新排序,我的意思是它可以
在发出字节码 byte 时由编译器重新排序,或在发出时由 JIT 重新排序
机器代码或 CPU 在乱序执行时(最终需要障碍来防止这种情况)相对于任何其他内存访问。
如果经常阅读,由于 Happens-Before 关系 (HBR),无法重新排序对 volatile
变量的访问。
我发现在每两个连续的(按程序顺序)动作之间存在一个 HBR 一个给定的线程,但它们可以重新排序。
也是一个易失性访问 HB,只能访问相同的 variable/field。
我认为 volatile
不可重新订购的是这个
A write to a volatile field (§8.3.1.4) happens-before every subsequent read [of any thread] of that field.
如果有其他线程变量的重新排序将变得可见 简单的例子
volatile int a, b;
Thread 1 Thread 2
a = 1; while (b != 2);
b = 2; print(a); //a must be 1
所以不是 HBR 本身阻止了排序,而是 volatile
扩展了与其他线程的这种关系,其他线程的存在是阻止重新排序的因素.
如果编译器可以证明 volatile 变量的重新排序不会改变
程序语义它可以重新排序即使有 HBR.
如果一个 volatile 变量除了它的访问之外永远不会被其他线程访问 可以重新排序
volatile int a, b, c;
Thread 1 Thread 2
a = 1; while (b != 2);
b = 2; print(a); //a must be 1
c = 3; //c never accessed by Thread 2
我认为 c=3
可以在 a=1
之前重新排序,这来自规范
确认这一点
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
所以我制作了这些简单的 java 程序
public class vtest1 {
public static volatile int DO_ACTION, CHOOSE_ACTION;
public static void main(String[] args) {
CHOOSE_ACTION = 34;
DO_ACTION = 1;
}
}
public class vtest2 {
public static volatile int DO_ACTION, CHOOSE_ACTION;
public static void main(String[] args) {
(new Thread(){
public void run() {
while (DO_ACTION != 1);
System.out.println(CHOOSE_ACTION);
}
}).start();
CHOOSE_ACTION = 34;
DO_ACTION = 1;
}
}
在这两种情况下,两个字段都标记为 volatile
并使用 putstatic
访问。
由于这些是 JIT 拥有的所有信息1,机器代码将是相同的,
因此 vtest1
访问不会被优化2.
我的问题
易失性访问是否真的从未按规范重新排序,或者它们可能是3,但实际上从未这样做过?
如果永远无法对易失性访问进行重新排序,那么规范的哪些部分是这样说的?这是否意味着 CPUs?
1或者JIT可以知道一个字段永远不会被其他线程访问?如果是,如何?
2例如内存障碍。
3例如,如果不涉及其他线程。
JLS 所说的(来自 JLS-8.3.1.4. volatile Fields)部分是
The Java programming language provides a second mechanism,
volatile
fields, that is more convenient than locking for some purposes.A field may be declared
volatile
, in which case the Java Memory Model ensures that all threads see a consistent value for the variable (§17.4).
这意味着访问可能被重新排序,但任何重新排序的结果必须最终一致(当被另一个线程访问时)与原始订单。单线程应用程序中的字段 不需要 锁定(来自 volatile
或 synchronization
)。
我将使用来自 JLS §17.4.5 的符号。
在你的第二个例子中,(如果你能原谅我的松散符号)你有
线程 1 排序:
hb(a = 1
, b = 2
)
hb(b = 2
, c = 3
)
不稳定的保证:
hb(b = 2
, b != 2
)
hb(a = 1
, 为 print
访问 a
)
线程 2 排序:
hb(while(b != 2);
, print(a)
)
我们有(强调我的)
More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.
c=3
和线程 2 之间没有发生之前的关系。实现可以自由地重新排序 c=3
到它的核心内容。
The memory model describes possible behaviors of a program. An implementation is free to produce any code it likes, as long as all resulting executions of a program produce a result that can be predicted by the memory model.
This provides a great deal of freedom for the implementor to perform a myriad of code transformations, including the reordering of actions and removal of unnecessary synchronization.
Java 内存模型为正确同步的程序提供顺序一致性 (SC)。简单来说,SC 意味着如果某个程序的所有可能执行都可以通过不同的执行来解释,其中所有内存操作都按某种顺序执行,并且该顺序与每个线程的程序顺序 (PO) 一致,那么该程序与这些顺序执行一致;所以它是顺序一致的(因此得名)。
这实际上意味着 JIT/CPU/memory 子系统可以根据需要重新排序易失性写入和读取,只要存在也可以解释实际执行结果的顺序执行。所以实际的执行并不那么重要。
如果我们看下面的例子:
volatile int a, b, c;
Thread 1 Thread 2
a = 1; while (c != 1);
b = 1; print(b);
c = 1;
在a=1
和b=2
之间有一个happens before关系(PO),在c=2
和c=3
之间有一个happens before关系(PO)和一个happens在关系 c=1
和 c!=0
之前(可变变量规则)和在 c!=0
和 print(b)
(PO) 之间的关系之前发生。
由于happens before关系是传递的,所以a=1
和print(b)
之间存在happens before关系。所以从这个意义上说,它不能重新排序。但是,没有人证明发生了重新排序,所以仍然可以重新排序。