如果在围绕同步锁的循环中使用变量,是否会读取 "fresh from main memory"?

Will a variable be read "fresh from main memory" if used in a loop AROUND a synchronized lock?

请看下面的代码:

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

}

无论任何时候,当一个线程进入synchronized块时,是否会从主存中加载变量flag的值?

感谢您的详细解释,因为我不确定flag是否有happend-befores关系。从字面上看,标志不在同步块中。

更新1:

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

更新2:

我又更新了代码,我win10+JDK8系统上的代码可以正常停止,你觉得是正确还是偶然,因为没有在所有硬件系统上测试,所以需要理论指导。

关注问题:

循环条件(flag变量)是否和循环内部的同步块有happen-before关系,如果有happen-before关系,jvm是否保证flag变量从主存加载即使标志变量不在同步块中。

如果大家都认为没有happen-before关系,那你怎么解释当我去掉synchronized块后,代码会无限循环。当我添加它时,它会正常停止。这只是意外吗?

好的,仔细看一下您的代码,您拥有的还不够。对共享字段的访问在您的 synchronized 块之外,所以不,它不起作用。

此外,Java要求共享内存的读写都"synchronized"不知何故。使用synchronized keyworld,这通常意味着您需要在读取和写入时都使用它,并且您没有显示写入。

除此之外,用于给定字段集或共享内存的 "lock" 必须是用于读取和写入的相同锁。说真的,这里 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 是的,可以。不保证内存可见性,但不禁止。内存可以出于任何原因可见,甚至只是 "accidentally." 在 Intel 平台上,Intel 有一个 QPI 总线,可以绕过内存总线高速交换内存更新信息。然而,即使这也可以通过软件解决,所以最好只在需要的地方进行同步(提示:查看 AtomicBoolean。)

感谢@xTrollxDudex和@markspace提供的资料,loop部分的代码是从jvm层面观察的,如果没有 happens-before 关系和代码可以从 :

优化
       while (flag){

        synchronized (lock){
            // some work
        }

    }

至:

      if(flag){

        while (true){

            synchronized (lock){
                //some work
            }

        }

    }

为了保证线程可见性,我们需要避免这种优化,比如通过volatile关键字或者其他同步策略。 sync block在循环中的出现类似于增强型volatile关键字的作用,保证了前面变量的可见性,所以当我们第二次循环进入sync block时,可以看到latest .的变化,这就是循环可以正常停止的原因。看起来不错,但不是正确的同步方法,所以不要这样做。

详细解释见here

类似问题