我正在阅读有关JMM(Java内存模型)的内容,我可以理解缓存变量的刷新如何导致其他线程具有脏读。还有人提到,重新排序指令可能会导致并发问题,即使我理解重新排序指令的含义,我也不会理解它会如何导致并发问题。
例如,假设线程t1在启动test1()
时已获得锁定,现在即使编译器已经进行了一些优化并且存在一些重新排序,因为z = 4;
已向上或向下,现在因为t2在t1释放之前不会获得test2()
的锁定,所以test1()
(甚至在test2()
中)重新排序会导致并发问题/错误?
public class Testing {
private int z = 2;
public synchronized void test1(){
//some statement..
z = 4;
//some statement..
}
public synchronized void test2(){
//some statement..
System.out.println(z);
//some statement..
}
}
<小时/> 我明白,经过适当的同步后,重新排序不会导致问题,但即使编译器没有进行优化和重新排序仍然存在并发问题的可能性,如果没有同步,对吧?要明确我指的是this链接,我无法理解他们在重新排序之后关于并发问题的观点,因为正如我所说,如果没有同步,那么即使没有任何重新同步也会出现并发问题排序。
编辑:请弃用我的代码段,因为在查看评论后,它现在不能保持良好状态,我的更新问题如上所述。
答案 0 :(得分:2)
您不会看到使用单个变量重新排序的问题。但是拿两个......
int foo = 0;
boolean isFooSet = false;
...
// thread 1
foo = 42;
isFooSet = true;
...
// thread 2
while (!isFooSet) {/*waste some time*/} // we wait until the flag is set in the other thread
System.out.println(42/foo); //we can actually divide by zero here
因此,当线程1在foo
之前设置isFooSet
时,线程2可以反过来看到它们,这使得标志isFooSet
无用。
请注意,如果不重新排序此代码将非常安全(从0除以),因为您可以看到例如isFooSet
是否声明为volatile
,从而阻止将写入移至{ {1}} 之后写入foo
。 它还解决了其他非重新排序相关的可见性问题,但这是一个不同的故事
答案 1 :(得分:1)
重新排序JVM时会考虑之前发生的关系,并且不会对这些关系进行任何无效的重新排序。当您进行数据竞赛时,重新排序是一个问题,请参阅书籍Java Concurrency in Practice, 16.1.3
当一个变量被多个线程读取时会发生数据争用,但读取和写入不是由before-before排序的。正确同步的程序是没有数据竞争的程序;正确同步的程序表现出顺序一致性,这意味着程序中的所有操作似乎都以固定的全局顺序发生。
答案 2 :(得分:1)
我不会阅读页面和页面的链接,所以请原谅我。但我想我理解你问题的要点。我确实记得这个链接是JCIP的第2章或第3章。
编辑1:回答第二个问题:&#34;没有并发,没有重新排序&#34;:
我想添加的另一件事(这里的优秀答案集)是你分配给一个int所以任何赋值都是原子的。现在想象它是双重还是对象分配。如果没有适当的并发性(并且如果没有作为先决条件进行排序),则会出现&#34;该对象未正确构造的问题&#34;在test1中并用于test2。
例如:
SomeObject z = new SomeObject(yyy);
public void test1() {
z = new SomeObject(xxx);
}
public void test2() {
System.out.print(z);
}
因此,我的建议是阅读JCIP的前3章,以了解Java内存模型和这些问题。