指令的重新排序如何导致并发问题

时间:2017-05-19 20:15:44

标签: java multithreading concurrency

我正在阅读有关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链接,我无法理解他们在重新排序之后关于并发问题的观点,因为正如我所说,如果没有同步,那么即使没有任何重新同步也会出现并发问题排序。

编辑:请弃用我的代码段,因为在查看评论后,它现在不能保持良好状态,我的更新问题如上所述。

3 个答案:

答案 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内存模型和这些问题。