Volatile关键字全家桶
volatile的好兄弟JMM
在面试中说起volatile我们就不得不说起JMM(Java内存模型),JMM描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量,存储到内存和从内存中读取变量这样的底层细节。
JMM有以下规定:
- 所有的共享变量都存储于主内存,这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。
- 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。
- 线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量。
- 不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成。
volatile的三大特性?
可见性
每个线程操作数据的时候会把数据从主内存读取到自己的工作内存,如果他操作了数据并且写回了,他其他已经读取的线程的变量副本就会失效了,需要都数据进行操作又要再次去主内存中读取了。通俗的将就是一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。
禁止指令重排
首先需要清楚什么是指令重排?
为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序。后面我们会在单例模式中介绍到。
不保证原子性
这个特性其实指的就是:假设现在有N个线程对同一个变量进行累加也是没办法保证结果是对的,因为读写这个过程并不是原子性的。
你在哪些地方用过volatile以及面试中的坑!
首先当我们被问起在哪些地方用过Volatile时,我们可以回答在单例模式中使用过。
先帮自己复习下单例模式:
- 私有静态化对象:private static SingleDemo singledemo;(默认懒汉式)
- 私有化构造器
- public static SingleDemo get singledemo()方法来获取对象
因为单例模式在多线程情况下是不安全的,因此需要加入双端检索机制,双端检索机制的存在就是为了防止两个线程同时进入这个方法造成一次运行新建多个对象。
然而这也不是完全安全的,因为计算机存在指令重排,所以我们需要加入Volatile关键字。
重点:创建一个对象的基本过程:
步骤1:为对象分配内存空间
步骤2:初始化对象
步骤3:将这个地址指向对象
因为计算机的指令重排,在单线程情况下这三个步骤怎么颠倒都是无所谓的,他们之间不存在数据依赖关系,但是当另一个线程参与进来时,情况就不对劲了。
线程一执行:步骤1->步骤3 此时线程二来获取这个对象,但是这个对象还没有被初始化,这就出现了问题。
那么Volatile是怎样解决这个问题的呢?
内存屏障
java编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序。需要注意的是:volatile写是在前面和后面分别插入内存屏障,而volatile读操作是在后面插入两个内存屏障。
写操作:
读操作:
在一次面试中面试官问到了我,我没有回答出来,但是面试官用非常简短的语言帮我解答了:类似于AOP思想
那么volatile不具备原子性是怎么解决的呢?
对于我这种单线程小学生来说,之前很少考虑多线程问题,但是学习了volatile后会发现,在我们项目中的很多源码都是不安全的,例如num++。
num++这样的操作想必大家都用过,但是在多线程的情况下他是不安全的。例如有两个线程进行num++操作,每个线程加1000次,那么输出的结果会是2000吗?当然不是!
因此在次我们会引出原子类Atomic,当然要是对性能没有要求的话synchronized绝对也是很好的选择。
简单介绍一下这个Atomic,例如AtomicInteger这个类,其中的一个方法为getAndIncreamnet(),这个方法的意思就是调用一次这个方法,就会给这个类的对象的值+1。我们可以看到他调用的底层的Unsafe类下的方法,通过改变偏移量valueOffset的地址值来改变变量的值。
最后说一下volatile和synchronized有哪些区别?
-
volatile只能修饰实例变量和类变量,而synchronized可以修饰方法,以及代码块。
-
volatile保证数据的可见性,但是不保证原子性(多线程进行写操作,不保证线程安全);而synchronized是一种排他(互斥)的机制。
-
volatile用于禁止指令重排序:可以解决单例双重检查对象初始化代码执行乱序问题。
-
volatile可以看做是轻量版的synchronized,volatile不保证原子性,但是如果是对一个共享变量进行多个线程的赋值,而没有其他的操作,那么就可以用volatile来代替synchronized,因为赋值本身是有原子性的,而volatile又保证了可见性,所以就可以保证线程安全了。