不加volatile,线程不可见的问题,与理论的结果有出入?

下面这段代码是比较常见的证明线程之间不可见的例子,但是我想问的是如果将 Thread.sleep(2000);这段代码去了,为什么第一个线程的死循环就能够跳出,线程之间不是不可见的吗?
我猜测的两种答案:
1.后一个线程先执行结束了,initFlag的共享变量副本进入主内存了,第一个线程再执行。但是我将步骤打印出来,结果并不是这样。
2.之前查阅了其他博客,说是System.out.println内置同步锁,触发了缓存一致性协议!
于是乎,我有将System.out.println换成logeer,但是还是没有出现理论上线程之间不可见的答案。这两种推论都不成立。

头发都急白了,大神help!help!谁能够说明白,为什么将Thread.sleep(2000)去了,没有加volatile,但是线程之间还是可见了?

 /**
 * 线程之间是不可见的
 */
public class VolatileVisibilityTest {
    private static boolean initFlag  =false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            System.out.println("waiting.....data");
            while (!initFlag){

            }
            System.out.println("wait....data....end");
        }).start();

        Thread.sleep(2000);

        new Thread(()->predData()).start();
    }

    public static void predData(){
        System.out.println("pred....data");
        initFlag = true;
        System.out.println("pred....data....end");
    }
}
阅读 2.1k
1 个回答

说一下我的猜测和分析,不确定对不对。感觉可能是编译成的汇编导致的,所以我打印了生成的汇编。使用jdk1.8测试,

在不加voliate关键字时,线程的run方法总共被编译了三次,我用jitwatch查看汇编。
这是第一次编辑出来的
image.png

这是第三次编译出来的
image.png

下边是加上voliate关键字后,最后一次编译出来的代码
image.png

我汇编也不是很好,我的理解是:在不加voliate关键字时,jvm觉得initflag变量是线程独有的,其值永远都是false,所以最后编译出来的那个循环中就没有判断initFlag,是一个死循环了。
加上voliate关键字时,每次循环才会去从内存读initflag。

至于Thread.sleep(2000)的作用。这句话确实关系很大。因为jvm的优化和代码运行时间运行次数有关。所以当你sleep 2秒是,可以让线程中的while循环跑很多次,从而触发了编译优化,最终生成的死循环。如果你不加sleep语句,最终生成的就是第一张图片的汇编。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏