前言
volatile关键字是面试中常问的知识点,包括三点:可见性、有序性、非原子性
。接下来就说一下这三点。
JMM(Java Memory Model - Java内存模型)
每个 Java 线程都有⾃⼰的⼯作内存。操作数据,⾸先从主内存中读,得到⼀份拷⻉,操作完毕后再写回主内存
-
JMM可能带来可⻅性、原⼦性和有序性问题
- 可⻅性:是指某个线程对主内存内容的修改,应该⽴刻通知其它线程
- 有序性:是指指令是有序的,不会被重排
- 原⼦性:是指⼀个操作是不可分割的,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。需要
整体完整,要么同时成功,要么同时失败,不能执⾏⼀半就不执⾏
volatile 关键字
- 它是 Java 提供的⼀种轻量级同步机制,能保证可⻅性和有序性,但是不能保证原⼦性
volatile 可见性
- 发现在 38 行代码处更新了数据值,但 45 行的 while 循环还是一直在运行,无法执行 49 行代码的打印。程序真的一直在 while 的循环里一直执行吗?那就在循环里加个打印吧,如下图
- 此时发现程序竟然停止了,没有一直在循环里,而且值也更新成功了。那是为什么呢?其实是因为打印语句
System.out.println
里有synchronized
关键字修饰,也是触发可见性方法之一,如下图
- 那么不加打印如何解决这个问题呢?答案就是给类变量 num 加上 volatile 关键字。如下图
volatile 有序性
-
计算机在执⾏程序时,为了提⾼性能,编译器和处理器常常会对指令做重排,⼀般分以下三种
- 单线程环境⾥⾯确保程序最终执⾏结果和代码顺序执⾏的结果⼀致
- 处理器在进⾏重排序时必须要考虑指令之间的数据依赖性
- 多线程环境中线程交替执⾏,由于编译器优化重排的存在,两个线程中使⽤的变量能否保证⼀致性是⽆法确定的,结果⽆法预测
如图方法 test 中的 a、b 变量毫无关联,则编译器和处理器可能会先执行 b 变量的赋值,而不是一定按顺序从上往下执行
volatile 非原子性
- 就算加上了 volatile 关键字,也无法保证结果就是预想中的值。这是因为 num++ 操作会形成 4 条指令。如下图
- 要想保证数据正确性,可使用 JUC 提供的 int 原子封装类
AtomicInteger
来进行自增,如下图
volatile 使用场景
- 单例模式
- new SingletonDemo 可能会发生指令重排,所以加上 volatile 关键字修饰 instance 实例。如下图
结语
这就是 volatile 关键字面试中常问的知识点,希望对您有帮助。