台式机&硬件面试官最爱的 volatile 关键字,这些问题你都搞懂了没?


前言
volatile相关的知识点 , 在面试过程中 , 属于基础问题 , 是必须要掌握的知识点 , 如果回答不上来会严重扣分的哦 。
volatile关键字基本介绍
volatile可以看成是synchronized的一种轻量级的实现 , 但volatile并不能完全代替synchronized , volatile有synchronized可见性的特性 , 但没有synchronized原子性的特性 。
可见性即用volatile关键字修饰的成员变量表明该变量不存在工作线程的副本 , 线程每次直接都从主内存中读取 , 每次读取的都是最新的值 , 这也就保证了变量对其他线程的可见性 。
另外 , 使用volatile还能确保变量不能被重排序 , 保证了有序性 。
当一个变量定义为volatile之后 , 它将具备两种特性:
保证此变量对所有线程的可见性
禁止指令重排序优化
volatile与synchronized的区别:
1、volatile只能修饰实例变量和类变量 , 而synchronized可以修饰方法 , 以及代码块 。
2、volatile保证数据的可见性 , 但是不保证原子性 而synchronized是一种排他(互斥)的机制 , 既保证可见性 , 又保证原子性 。
3、volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞 。
4、volatile可以看作是轻量版的synchronized , volatile不保证原子性 , 但是如果是对一个共享变量进行多个线程的赋值 , 而没有其他的操作 , 那么就可以用volatile来代替synchronized , 因为赋值本身是有原子性的 , 而volatile又保证了可见性 , 所以就可以保证线程安全了 。
保证此变量对所有线程的可见性:
当一条线程修改了这个变量的值 , 新值对于其他线程可以说是可以立即得知的 。 Java内存模型规定了所有的变量都存储在主内存 , 每条线程还有自己的工作内存 , 线程的工作内存保存了该线程使用到的变量在主内存的副本拷贝 , 线程对变量的所有操作都必须在工作内存中进行 , 而不能直接读取主内存中的变量 。
知识拓展:内存可见性:
概念:JVM内存模型:主内存 和 线程独立的 工作内存 。 Java内存模型规定 , 对于多个线程共享的变量 , 存储在主内存当中 , 每个线程都有自己独立的工作内存(比如CPU的寄存器) , 线程只能访问自己的工作内存 , 不可以访问其它线程的工作内存 。 工作内存中保存了主内存共享变量的副本 , 线程要操作这些共享变量 , 只能通过操作工作内存中的副本来实现 , 操作完毕之后再同步回到主内存当中 。
如何保证多个线程操作主内存的数据完整性是一个难题 , Java内存模型也规定了工作内存与主内存之间交互的协议 , 定义了8种原子操作:
lock:将主内存中的变量锁定 , 为一个线程所独占 。
unclock:将lock加的锁定解除 , 此时其它的线程可以有机会访问此变量 。
read:将主内存中的变量值读到工作内存当中 。
load:将read读取的值保存到工作内存中的变量副本中 。
use:将值传递给线程的代码执行引擎 。
assign:将执行引擎处理返回的值重新赋值给变量副本 。
store:将变量副本的值存储到主内存中 。
write:将store存储的值写入到主内存的共享变量当中 。
通过上面Java内存模型的概述 , 我们会注意到这么一个问题 , 每个线程在获取锁之后会在自己的工作内存来操作共享变量 , 操作完成之后将工作内存中的副本回写到主内存 , 并且在其它线程从主内存将变量同步回自己的工作内存之前 , 共享变量的改变对其是不可见的 。
即其他线程的本地内存中的变量已经是过时的 , 并不是更新后的值 。 volatile保证可见性的原理是在每次访问变量时都会进行一次刷新 , 因此每次访问都是主内存中最新的版本 。 所以volatile关键字的作用之一就是保证变量修改的实时可见性 。


推荐阅读