在Java内存模型一节,除了synchronized外,我们还提到一个常用关键词----volatile,我们说过volatile保证了并发环境的可见性和顺序性,使用volatile修饰的变量,当然值发生改变时,可以同步到其他线程,其他线程按新值进行计算,下面我们编写代码来看下volatile关键词的使用和实现原理,验证我们前面抛出的结论。
当我们使用多线程对共享变量进行操作时,如果不用volatile,会是什么现象呢?以线程一节描述的使用标识位停止线程执行为例说明,代码如下:
public class VolatileTest {
// 标志位
boolean mInterrupted = false;
// 设置标志位为true,打断线程运行
public void needInterrupt(boolean isNeedInterrupt) {
this.mInterrupted = isNeedInterrupt;
System.out.println("修改mInterrupted值为:" + mInterrupted + ",time:" + System.currentTimeMillis());
}
public static void main(String[] args) {
VolatileTest test = new VolatileTest();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "开始执行,time:" + System.currentTimeMillis());
while (!test.mInterrupted) {
}
System.out.println(Thread.currentThread().getName() + "结束执行,time:" + System.currentTimeMillis());
}, "CustomThread1").start();
new Thread(() -> {
try {
Thread.sleep(2000);
test.needInterrupt(true);
System.out.println(Thread.currentThread().getName() + "打断关联的线程执行,time:" + System.currentTimeMillis());
} catch (InterruptedException e) {
}
}, "CustomThread2").start();
}
}
随后运行,输出如下:
可以看出在设置标志位为true后,很长一段时间仍未打印CustomThread1停止运行的日志,换而言之,我们虽然在CustomThread2中修改了mInterrupted的值为true,但是CustomThread1中值仍然为false,明显不符合我们预期,那么加上volatile修饰又会怎样呢?修改mInterrupted声明,添加volatile修饰,运行结果如下:
可以看出在使用volatile修饰后,在CustomThread2中执行打断后,CustomThread1立即停止运行了。
volatile实现原理
在synchronized实现原理中,我们了解到可以从字节码和汇编码两个角度去了解synchronized的实现,对于volatile,我们不妨也做相同假设,首先我们获取VolatileTest的字节码,看是否有相关实现,字节码代码如下:
从字节码可以看出,针对mInterrupted变量而言,除了声明时多了一个ACC_VOLATILE标记,在使用过程中无明显变化。
看来字节码中并没有volatile的核心实现,我们继续使用hsdis获取VolatileTest类的汇编码,进一步确定其实现,字节码如下:
可以看到在更新mInterrupted值时,增加了lock addl $0x0,(%rsp)指令,该指令就使得当前对共享变量的修改对其他线程立即生效,而lock addl是内核中的写屏障指令,所以我们一般说volatile关键词底层是通过内存屏障指令实现的。在有volatile修饰的变量需要赋值时,处理器会插入内存屏障指令,将当前线程工作内存的值刷新进主存,同时使得其他线程工作内存中引用的该变量的缓存地址失效,重新从主存同步新值,完成业务逻辑。
volatile与synchronized
volatile与synchronized区别见下表:
关键字 | 并发特性 | 作用级别 | 是否阻塞其他线程 | 字节码实现 | 汇编实现 | 备注 |
---|---|---|---|---|---|---|
volatile | 顺序性,可见性 | 变量 | 否 | / | lock addl | |
synchronized | 顺序性,可见性,原子性 | 变量,函数 | 是 | monitorenter/monitorexit/ACC_SYNCHRONIZED | lock cmpxchg |