barrier cream是什么意思 barrier

点击上方"java全栈技术"关注,每天学习一个java知识点
volatile修饰符并不是Java语言首创,早在C和C++当中就已经存在 。在讲解Java的volatile关键字之前,有必要先了解一下Java的内存模型 。
Java内存模型简称JMM(Java Memory Model),是Java虚拟机所定义的一种抽象规范,用来屏蔽不同硬件和操作系统的内存访问差异,让java程序在各种平台下都能达到一致的内存访问效果 。
Java内存模型长成什么样子呢?就是下图的样子:
barrier cream是什么意思 barrier

文章插图
这里需要解释几个概念:
1.主内存(Main Memory)
主内存可以简单理解为计算机当中的内存,但又不完全等同 。主内存被所有的线程所共享,对于一个共享变量(比如静态变量,或是堆内存中的实例)来说,主内存当中存储了它的“本尊” 。
2.工作内存(Working Memory)
工作内存可以简单理解为计算机当中的CPU高速缓存,但又不完全等同 。每一个线程拥有自己的工作内存,对于一个共享变量来说,工作内存当中存储了它的“副本” 。
线程对共享变量的所有操作都必须在工作内存进行,不能直接读写主内存中的变量 。不同线程之间也无法访问彼此的工作内存,变量值的传递只能通过主内存来进行 。
之所以所有线程没有直接在主内存上操作,是因为直接操作主内存太慢了,JVM不得不利用性能较高的工作内存 。这里可以类比一下CPU、高速缓存、内存直接的关系 。
以上说的这些可能有点抽象,看下面这个例子:
对于一个静态变量
static int s = 0;
线程A执行如下代码:
s = 3;
那么,JMM的工作流程如下图所示:
barrier cream是什么意思 barrier

文章插图

barrier cream是什么意思 barrier

文章插图

barrier cream是什么意思 barrier

文章插图

barrier cream是什么意思 barrier

文章插图
通过一系列内存读写的操作指令(JVM内存模型共定义了8种内存操作指令),线程A把静态变量 s=0 从主内存读到工作内存,再把 s=3 的更新结果同步到主内存当中 。从单线程的角度来看,这个过程没有任何问题 。
这时候我们引入线程B,执行如下代码:
System.out.println("s=" + s);
那么,如果线程A先执行,线程B后执行,线程B的输出结果会是什么?
实际结果会有两种:
s=3 或者 s=0
分析:
引入线程B以后,当线程A首先执行,更大的可能是出现下面情况:
barrier cream是什么意思 barrier

文章插图

barrier cream是什么意思 barrier

文章插图

barrier cream是什么意思 barrier

文章插图

barrier cream是什么意思 barrier

文章插图
此时线程B从主内存得到的s值是3,理所当然输出 s=3,这种情况不难理解 。但是,有较小的几率出现另一种情况:
barrier cream是什么意思 barrier

文章插图

barrier cream是什么意思 barrier

文章插图

barrier cream是什么意思 barrier

文章插图

barrier cream是什么意思 barrier

文章插图
因为工作内存所更新的变量并不会立即同步到主内存,所以虽然线程A在工作内存当中已经把变量s的值更新成3,但是线程B从主内存得到的变量s的值仍然是0,从而输出 s=0 。
如何解决这个问题
一般的做法是使用Synchronized同步锁,虽然可以保证线程安全,但是Synchronized是重量级锁,对程序的性能影响太大 。还有一种轻量级的解决办法,也就是我们今天要的volatile 。
volatile关键字具有许多特性,其中最重要的特性就是保证了用volatile修饰的变量对所有线程的可见性 。
这里的可见性是什么意思呢?当一个线程修改了变量的值,新的值会立刻同步到主内存当中 。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值 。
为什么volatile关键字可以有这样的特性?这得益于java语言的先行发生原则(happens-before) 。先行发生原则在维基百科上的定义如下:

秒懂生活扩展阅读