面试必考之volatile
一、前提知识回顾
1.并发和并行
1.1 举例1
- 吃饭吃到一半,电话来了,你一直到吃完了以后才去接,说明你不支持并发也不支持并行。
- 你吃饭吃到一半,电话来了,你停下来接了电话,接完后继续吃饭,这说明你支持并发(不一定是同时的)
- 你吃饭到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
1.2 举例2
- 并发指在一台CPU上“同时”(也就是快速切换)处理多个任务。
- 并行指在多台CPU上同时处理多个任务(好比hadoop分布式集群)。
二、Volatile
1.Volatile是什么
- volatile是Java虚拟机提供的轻量级的同步机制
2. Volatile的三大特性
- 保证可见性
- 不保证原子性
- 禁止指令重排
2.1 验证volatile的可见性
- 假如int number=0;number变量之前根本没有添加volatile关键修饰,没有可见性
- 添加了volatile,可以解决可见性问题。
2.2 验证volatile不保证原子性
原子性指的是什么?
- 不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。需要整体完整,要么同时成功,要么同时失败。
失败原因(出现写覆盖,数据丢失)
2.3 指令重排
- 计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排,一般分以下3种。
- 单线程环境里确保程序最终执行结果和代码顺序执行结果一致
- 处理器在进行重排序必须要考虑指令之间的数据依赖性
- 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
指令重排小结:
number++在多线程下是非线程安全的,如何不加synchronized解决
- 加synchronized太重了
- 直接使用我们juc 下AtomicInteger
3.对Volatile变量进行写、读操作
内存屏障概念(Memory Barrier)
- 内存屏障又称内存栅栏,是一个CPU 指令,它的作用有两个
- 保证特定操作的执行顺序
- 保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)
- 通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化
4. JVM是什么?
4.1 JMM(java内存模型)
- JMM(Java Memory Model)本身是一种抽象的概念,并不存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式
- 不能直接访问主内存,而是各个线程先拉到自己的工作内存中,修改,在写入主内存,内存被修改后提醒其他线程
-
JMM关于同步的规定
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
4.2 JMM的三大特性
- 可见性
- 原子性
- 有序性
4.3 线程安全性获得保证
工作内存与主内存同步延迟现象导致的可见性问题
- 可以使用synchronized或volatile关键字解决,他们都可以使得一个线程修改后的变量立即对其他线程可见
对于指令重排导致的可见性问题的有序性问题 - 可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化
4.4 JMM小结
- 各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存中进行操作后在写回到主内存中的,当AAA线程已经对主内存进行修改,那么会通知到BBB这也体现了其可见性。
5.在哪些地方用到过volatile?
5.1 单例模式DCL代码
Double Check Lock 双端检索机制
注意:如果没有volatile,该机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁
止指令重排。
参照
学习课程:https://www.bilibili.com/video/BV18b411M7xz?p=20