注意
通过说存储器访问可以(或不能)重新排序我和它可以
发送字节代码字节时由编译器重新排序,或者在发送时由JIT重新排序
机器代码或CPU在执行任何其他内存访问时无序执行(最终需要屏障来防止这种情况)。
如果经常读到由于 Happens-Before关系(HBR)而无法重新排序对volatile
变量的访问。
我发现在每两个连续(按程序顺序)动作之间存在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
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
之前重新排序,这个引用来自规范
确认这个
应该注意的是,之前存在一种发生在之前的关系 两项行动并不一定意味着它们必须按照该顺序进行 在实施中。如果重新排序产生的结果与合法的一致 执行,这不违法。
所以我制作了这些简单的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 ,但这在实践中从未实现过?
如果永远不能重新排序易失性访问,那么规范的哪些部分会这样说?这是否意味着CPU执行所有易失性访问并按程序顺序查看?
1 或者JIT可以知道其他线程永远不会访问某个字段?如果是,怎么样?。
2 例如,存在记忆障碍。
3 例如,如果没有涉及其他线程。
答案 0 :(得分:2)
JLS所说的内容(来自JLS-8.3.1.4. volatile Fields)部分是
Java编程语言提供了第二种机制
indexVar
字段,这比某些目的的锁定更方便。字段可以声明为
volatile
,在这种情况下,Java内存模型可确保所有线程都看到变量的一致值(§17.4)。
这意味着可以重新排序访问 ,但任何重新排序的结果必须最终与原始订单一致(当由另一个线程访问时)。单线程应用程序中的字段不会需要锁定(来自volatile
或volatile
)。
答案 1 :(得分:1)
Java 内存模型为正确同步的程序提供顺序一致性 (SC)。 SC 简单来说意味着如果某个程序的所有可能执行都可以用不同的执行来解释,其中所有内存操作都以某种顺序执行,并且该顺序与每个线程的程序顺序 (PO) 一致,则该程序与这些顺序执行一致;所以它是顺序一致的(因此得名)。
这实际上意味着 JIT/CPU/内存子系统可以根据需要重新排序易失性写入和读取,只要存在顺序执行也可以解释实际执行的结果。所以实际执行并不那么重要。
如果我们看下面的例子:
volatile int a, b, c;
Thread 1 Thread 2
a = 1; while (c != 1);
b = 1; print(b);
c = 1;
在 a=1
和 b=2
(PO) 之间的关系之前发生了一个发生在 c=2
和 c=3
之间的关系之前 (PO) 和一个发生在关系之前c=1
和 c!=0
(易失性变量规则)以及发生在 c!=0
和 print(b)
(PO) 之间的关系之前。
由于发生在关系之前发生是可传递的,因此在 a=1
和 print(b)
之间存在发生在关系之前。所以从这个意义上说,它不能重新排序。但是,没有人证明发生了重排序,因此仍然可以重排序。
答案 2 :(得分:0)
我将使用JLS §17.4.5中的符号。
在你的第二个例子中,(如果你原谅我的松散符号),你有
线程1排序:
hb(a = 1
,b = 2
)
hb(b = 2
,c = 3
)
挥发性保证:
hb(b = 2
,b != 2
)
hb(a = 1
,a
访问print
线程2排序:
hb(while(b != 2);
,print(a)
)
我们有(强调我的)
更具体地说,如果两个动作共享发生在之前的关系, 他们不一定必须按顺序发生 他们不与之分享的任何代码 关系即可。写入与读取数据竞争中的一个线程 例如,在另一个线程中,可能看起来不按顺序发生 那些读物。
c=3
和线程2之间没有发生过 - 之前的关系。实现可以自由地重新排序c=3
到它的内容。
答案 3 :(得分:0)
内存模型描述了程序的可能行为。一个实现可以自由地产生它喜欢的任何代码,只要程序的所有结果执行产生的结果可以被内存模型预测。
这为实现者提供了很大的自由来执行无数代码转换,包括重新排序操作和删除不必要的同步。