如果在同步锁周围的循环中使用变量,是否会从主存储器中读取该变量?

时间:2018-07-09 02:59:14

标签: java multithreading synchronized happens-before

请参见下面的代码:

private  static boolean flag=true; // main thread will call flag=false

private final static Object lock=new Object(); // lock condition

public static void thread1(){

    while (flag){

        synchronized (lock){
            // some work
        }

    }

}


public static void main(String[] args) throws Exception {

    Thread t1=new Thread(()->{
        thread1();
    });
    t1.start();
    Thread.sleep(1000);
    flag=false;

    // The program can stop normally

}

无论何时,当一个线程进入同步块时,是否会从主存储器中加载变量标志的值?

谢谢您的详细解释,因为我不确定该标志是否具有事前关系。从字面上看,该标志不在同步块中。

更新1:

我知道使用volatile可以,而且我也知道如何编写正确的代码,但是我现在想知道是否没有volatile关键字。是否同步可以保证可见性。注意:flag变量不在同步块中。

更新2:

我再次更新了代码,我的win10 + JDK8系统上的代码可以正常停止,您认为它是正确的还是偶然的,因为尚未在所有硬件系统上进行测试,因此我需要理论指导。

关注问题:

循环条件(标志变量)是否与循环内的同步块具有先发生关系,如果存在先发生关系,jvm是否确保已从主存储器加载标志变量,即使标志变量不在同步块中。

如果每个人都认为没有事前发生的关系,那么您如何解释当我删除同步块时,代码将无限期地循环。当我添加它时,它将正常停止。这只是意外吗?

2 个答案:

答案 0 :(得分:3)

再仔细一点看代码,您所拥有的还不够。对共享字段的访问位于您的synchronized块之外,因此不行。

此外,Java要求共享内存的读取和写入都必须以某种方式“同步”。使用synchronized键域,通常意味着您需要在读取和写入时都使用它,而没有显示写入。

此外,用于给定的一组字段或共享内存的“锁”对于读取和写入必须是相同的锁。严重的是,volatile在这里要容易得多,java.util.concurrent中的API甚至更容易并且建议使用。不要尝试重新发明轮子。

private static boolean flag = true; // must use 'resetFlag'

public void resetFlag() { synchronized( "lock" ) {flag = false;} }

public boolean getFlag() { synchronized( "lock" ) {return flag;} }

public void thread1() {
    while ( getFlag() ){
        synchronized ("lock"){
            // other work
        }
    }
}

public static void main(String[] args) throws Exception {

    Thread t1=new Thread(()->{
        thread1();
    });
    t1.start();
    Thread.sleep(1000);
    resetFlag();

    // The program can stop normally

}

我认为以上内容是必需的更改。

关于第二次更新:the code on my win10+JDK8 system can stop normally是的。不能保证内存可见性,但不是禁止的。可以出于任何原因使内存可见,即使只是“偶然”。在Intel平台上,Intel具有QPI总线,可绕过内存总线高速交换内存更新信息。但是,即使可以通过软件解决,所以最好只是将同步放在需要的位置(提示:查看AtomicBoolean。)

答案 1 :(得分:1)

由于@xTrollxDudex和@markspace提供的信息,循环部分中的代码是从jvm级别观察的,如果没有 事前发生关系,并且代码可以从:

进行优化
       while (flag){

        synchronized (lock){
            // some work
        }

    }

至:

      if(flag){

        while (true){

            synchronized (lock){
                //some work
            }

        }

    }

为确保线程可见性,我们需要避免这种优化,例如通过volatile关键字或其他同步策略。 循环中同步块的外观类似于增强型volatile关键字的功能,该功能保证了变量前面的可见性,因此当我们第二次循环进入同步块时,可以看到最新的。更改,这就是循环可以正常停止的原因。看起来不错,但这不是正确的同步方法,所以不要这样做。

有关详细说明,请参见here

中的类似问题