前段时间在我一个干饭群里有这样一个讨论:
上面的问题概括来说,是这样:
一个类中有以下属性:
一个 static boolean 类型的变量 initFlag ,默认值是 false
一个 static Integer 类型的变量 a ,默认值是 0
该类的 main 方法中有两个线程,逻辑如下:
线程 1 的执行逻辑是:
1 2 3 |
while(!initFlag){ } System.out.printfln("1"); |
单看这段代码,意思是:如果 initFlag 是 false 则 while 循环会一直持续,当 initFlag 变成 true 之后,跳出 while 循环,执行下面的 System.out.printfln(“1”); 语句。
线程 2 的执行逻辑是:
1 2 |
initFlag = true; System.out.printfln(initFlag); |
单看这段代码,意思是:设置 initFlag = true ,然后执行 System.out.printfln(initFlag); 输出语句。
线程 1 和线程 2 中间有一个 sleep(500) 的睡眠。
问题是:执行 main 方法后该程序输出结果是什么?
第一次分析
上面的线程 1 第 19 行 while 中 !initFlag 为 true ,然后会卡在 while 循环中 ,然后下面的线程 2 修改 initFlag 为true,然后输出 true ,initFlag 改为 true 了,那么第 19 行的 !initFlag 就是 false 了,上面的线程 1 应该跳出 while 循环,然后输出1,线程 1 输出 1 和 线程 2 输出 true 的顺序可能会变。
然而实际上输出:
说明了第 19 行程序读到的 initFlag 还是 false,但 initFlag 命名已经被线程 2 改为 true 了啊,所以问题是:为什么线程 1 中的 while(!initFlag) 没有读到修改后的 initFlag 值?
第二次分析
尝试做些改动,比如在线程 1 的 while 循环中加些代码,比如 System.out.printfln(“…”) ,结果会发现线程 1 能跳出 while 循环。
可能的原因?
猜测1
我猜测可能是 JVM 对线程 1 中的代码做了优化,大致是类似于缓存了 initFlag 的值是 false ,所以 线程 2 中修改了 initFlag 而线程 1 中没有读到,即生成的 class 文件肯定不是源码中线程 1 的逻辑,我尝试分析 class 文件中的逻辑,但没看太懂~
猜测2
我同事猜测的原因是同步锁之类的原因。。
真正原因
是因为 JIT (即时编译,Just-in-Time Compilation)对线程 1 中的代码做了『表达式提升』,将 initFlag 的值提升到了表达式之外,类似于修改成:
1 2 3 4 |
initFlag1 = initFlag; while(!initFlag1){ } System.out.printfln("1"); |
所以,才会出现线程 1 中 while 循环下面的 println 语句不执行(即代码一直卡在 while 循环中)。
而在 while 循环内部加上 println 语句则会阻止 JIT 做『表达式提升』。
关于 JIT 的 『表达式提升』 后面文章再讲。