3.1 关于线程的可见性问题分析

可见性和有序性是两个独立的问题,但是彼此又有关联,笔者将分别进行说明。

什么是可见性?如果一个线程对一个共享变量进行了修改,而其他线程不能及时地读取修改之后的值,那么我们认为在多线程环境下该共享变量存在可见性问题,举个具体的例子如下。

代码的逻辑很简单,首先t1线程通过stop变量来判断是否应该终止循环,然后在main线程中通过修改stop变量的值来破坏t1线程的循环条件从而退出循环。但是,实际情况是t1线程并没有按照期望的结果执行,该线程一直处于运行状态。

3.1.1 思考导致问题的原因

上述程序的问题只有在HotSpot的Server模式中才会出现,在HotSpot虚拟机中内置了两个即时编译器,分别是Client Compiler(C1编译器)和Server Compiler(C2编译器),程序使用哪个编译器取决于JVM虚拟机的运行模式。

Server Compiler是专门面向服务器端的、充分优化过的高级编译器。它有一些比较典型的优化功能,如无用代码消除(Dead Code Elimination)、循环展开(Loop Unrolling)、循环表达式外提(Loop Expression Hoisting)、消除公共子表达式(Common Subexpression Elimination)等。下面我们在VolatileExample这个例子中,通过Server Compiler中的循环表达式外提(Loop Expression Hoisting)进行代码优化,具体如下。

从上面代码中我们发现,被优化的代码对stop变量不具备变化的能力,因此会导致当其他线程修改stop的值时,该线程无法读取。为了防止因JIT优化而产生问题,我们可以增加一个JVM参数:

再次运行VolatileExample程序,发现能够正常执行结束。但是通过JVM参数来禁止JIT优化是全局的操作,会影响整个程序的优化,代价有点大,有没有更好的方式呢?

3.1.2 volatile关键字解决可见性问题

在Java中提供了一个volatile关键字,如果我们针对stop变量增加一个volatile关键字,并再次运行相同的代码,就会发现t1线程能够正常结束。

由此可见,volatile可以禁止编译器的优化,在多处理器环境下保证共享变量的可见性。