一个线程安全的问题

前段时间在我一个干饭群里有这样一个讨论:


问题描述

上面的问题概括来说,是这样:

一个类中有以下属性:
一个 static boolean 类型的变量 initFlag ,默认值是 false
一个 static Integer 类型的变量 a ,默认值是 0

该类的 main 方法中有两个线程,逻辑如下:

线程 1 的执行逻辑是:

单看这段代码,意思是:如果 initFlag 是 false 则 while 循环会一直持续,当 initFlag 变成 true 之后,跳出 while 循环,执行下面的 System.out.printfln(“1”); 语句。

线程 2 的执行逻辑是:

单看这段代码,意思是:设置 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 中 while 循环下面的 println 语句不执行(即代码一直卡在 while 循环中)。

而在 while 循环内部加上 println 语句则会阻止 JIT 做『表达式提升』。

关于 JIT 的 『表达式提升』 后面文章再讲。

完。

码先生
Author: 码先生

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注