在阅读本文前,请思考以下的面试题?
-
volatile 是什么?
-
volatile 的特性
-
volatile 是如何保证可见性的?
-
volatile 是如何保证有序性的?
-
volatile 可以保证原子性吗?
-
使用 volatile 变量的条件是什么?
-
volatile 和 synchronized 的区别
-
volatile 和 atomic 原子类的区别是什么?
这一章主要是讲解 volatile 的原理,在开始本文前,我们来看一张 volatile 的思维导图,先有个直观的认识。
什么是 volatile?
目前的操作系统大多数都是多 CPU,当多线程对一个共享变量进行操作时,会出现数据一致性问题
Java 编程语言允许线程访问共享变量,那么为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量,或者把这个变量声明成 volatile,可以理解 volatile 是轻量级的 synchronized。
使用 volatile 可以在 Java 线程内存模型确保所有线程看到这个变量的值是一致的,在多个处理器中保证了共享变量的“可见性”。
volatile 两核心三性质?
两大核心:JMM 内存模型(主内存和工作内存)以及 happens-before
三条性质:原子性,可见性,有序性
volatile 性质?
-
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
-
禁止进行指令重排序。(实现有序性)
-
只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。(不能实现原子性)
-
volatile 不会引起上下文的切换和调度
总结:volatile 保证了可见性和有序性,同时可以保证单次读/写的原子性
相关的 Cpu 术语说明
什么是可见性?
在单核 cpu 的石器时代,我们所有的线程都是在一颗 CPU 上执行,CPU 缓存与内存的数据一致性容易解决。因为所有线程都是操作同一个 CPU 的缓存,一个线程对缓存的写,对另外一个线程来说一定是可见的。
例如在下面的图中,线程 A 和线程 B 都是操作同一个 CPU 里面的缓存,所以线程 A 更新了变量 a 的值,那么线程 B 之后再访问变量 a,得到的一定是 a 的最新值(线程 A 写过的值)。
在多核 CPU 的时代,每颗 CPU 都有自己的缓存,这时 CPU 缓存与内存的数据一致性就没那么容易解决了,当多个线程在不同的 CPU 上执行时,这些线程操作的是不同的 CPU 缓存。比如下图中,线程 A 操作的是 CPU-1 上的缓存