【发布时间】:2014-12-30 17:04:27
【问题描述】:
我有一个关于 Java 内存模型的问题。举个例子:
action 1
action 2
synchronized(monitorObject) { //acquire
action 3
} //release
action 4
acquire 和release 可以是任何同步边(锁定、解锁、启动线程、加入线程、检测线程中断、volatile-write、volatile-read 等)
是否保证action 3 在获取之前 不能移动,在发布之后 不能移动?
是否保证action 2 不能在获取之后 移动(无论是在发布之前还是之后)并且action 4 不能在之前移动 em> 发布(无论是在获取之前还是之后)?
对于编译器的重新排序操作,与边“双向障碍”同步吗?
编辑 1 我担心这一点,因为如果 synchronizes-with 边缘不是双向重新排序障碍,编译器可以简单地通过将锁获取移动到其他锁来创建死锁。
或者双向重新排序障碍甚至不需要防止这种情况发生,因为锁获取不能被推入其他人,因为这会改变同步顺序?
编辑 2 动作 1、2、3 和 4 是 JMM 定义的“线程间动作”。
编辑 3 下面的示例显示了重新排序如何导致死锁:
x 和 y 是共享变量,syncA 和 syncB 可以被任何其他线程获取。但是使用下面的代码,就不会出现死锁了。
/* 1 */ synchronized(syncA) {
/* 2 */ x = 1;
/* 3 */ }
/* 4 */ y = 0;
/* 5 */ synchronized(syncB) {
/* 6 */ y = 1;
/* 7 */ }
但是,如果 syncA 的获取被重新排序到 syncB 块中,这可能会导致死锁:
y = 0;
synchronized(syncB) {
y = 1;
synchronized(syncA) {
x = 1;
}
}
我认为这不是合法的编译器转换,因为它会改变同步顺序。我对这个假设是否正确? Java 内存模型 (JMM) 的哪一部分允许/不允许这样做?
【问题讨论】:
-
@MinecraftShamrock 通过您的编辑,我认为您应该再次提出这个问题。显示您认为编译器可能通过“移动锁获取”“创建死锁”的位置。我开始在这里闻到 XY 问题。
-
@markspace:它可以进行一些有限的重新排序。笼统的陈述“当然,2 可能在 3 之前”是如此不准确,以至于没有用处。 (同意 X/Y 问题。)
-
@TJCrowder 但是如果它可以进行任何重新排序,那么当有人询问它是否可以重新排序时,您必须回答“是” .这是一般情况。 (我说 2 可能发生在 之后 3,而不是之前。)
-
排序 - hb 适用于线程中的操作,例如 T1 和 T2,因此您有 (1) 个程序顺序,这意味着 HB(T1-1, T1-3), hb(T1-3, T1-5) 等 + 由于同步块,T2 和 (2) 线程间顺序相同:如果 T1 在 T2 之前在 (1) 中获取监视器,则 hb(T1-3, T2-1) 那么你需要推理关于第二个街区(T1 或 T2 都可以先锁定)。如果你假设你的重新排序可能会造成不一致(现在无法查看详细信息)。
-
是的 - 这是相关的:stackoverflow.com/questions/19215948/… - 食谱的链接有你的答案
标签: java multithreading compiler-optimization memory-model java-memory-model