11 java volatile compiler-optimization jls
注意
通过说可以(或不能)重新排序存储器访问,并且它可以在发出字节代码字节时由编译器重新排序,或者在发出机器代码时由JIT重新排序,或者在执行顺序执行时由CPU重新排序(最终要求相对于任何其他内存访问,阻止这种情况的障碍.
如果经常读取volatile由于Happens-Before关系(HBR)而无法重新排序对变量的访问.
我发现在给定线程的每两个连续(按程序顺序)动作之间存在HBR,但它们可以重新排序.
也是易失性访问HB,只能访问相同的变量/字段.
我认为这是volatile不可重新解决的
写入易失性字段(第8.3.1.4节)发生在每个后续读取[该字段的任何线程]之前.
如果存在其他线程,则变量的重新排序将变为可见,如此简单示例中所示
volatile int a, b;
Thread 1 Thread 2
a = 1; while (b != 2);
b = 2; print(a); //a must be 1
Run Code Online (Sandbox Code Playgroud)
因此,不是HBR本身阻止了排序,而是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
Run Code Online (Sandbox Code Playgroud)
我认为之前c=3可以很好地重新订购a=1,这些规格的引用证实了这一点
应该注意的是,两个动作之间存在的先发生关系并不一定意味着它们必须在实现中以该顺序发生.如果重新排序产生的结果与合法执行一致,则不是非法的.
所以我制作了这些简单的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;
}
}
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,两个字段都标记为volatile和访问putstatic.由于这些都是JIT具有信息1,本机代码将是相同的,因此vtest1访问不会被优化2.
易失性访问是否真的从未按规范重新排序,或者它们可能是3,但这在实践中从未实现过?
如果永远不能重新排序易失性访问,那么规范的哪些部分会这样说?这是否意味着所有易失性访问都是由CPU执行并按程序顺序查看的?
1或者JIT可以知道其他线程永远不会访问某个字段?如果有,怎么样?
2个记忆障碍将存在例如.
3例如,如果不涉及其他线程.
我将使用JLS \xc2\xa717.4.5中的符号的符号。
\n\n在你的第二个例子中,(如果你能原谅我松散的符号)你有
\n\n线程 1 排序:
\nhb( a = 1, b = 2)
\nhb( b = 2,c = 3 )
易失性保证:
\nhb( b = 2, b != 2)
\nhb( a = 1,a访问print)
线程 2 排序:
\nhb( while(b != 2);,print(a) )
我们有(强调我的)
\n\n\n\n\n更具体地说,如果两个操作共享“发生前”关系,\n 对于不与它们共享“发生在”关系的任何代码,\n 它们不一定必须按该顺序发生\n。例如,一个线程中的写入与另一线程中的读取发生数据竞争,这些读取可能会出现乱序。
\n
和线程 2之间没有发生之前的关系。c=3该实现可以自由地重新排序c=3其核心内容。