需要了解的内容: JMM内存模型描述
一、简介(请你谈谈对volatile的理解)
首先,volatile是Java虚拟机提供的轻量级的同步机制,他基本遵守了JMM的规范。
用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此不会使执行线程阻塞。
二、三大特性
volatile主要有三大特性:保证可见性,有序性(即禁止指令重排序),但是不保证原子性。
什么叫保证了可见性?
可见性:一个线程修改了主物理内存的值,其他线程马上获取主内存的值。
禁止指令重排序
有序性:为了提高性能,编译器和处理器都会对指令做重排,一般分为:
源代码-> 编译器优化的重排->指令并行的重排->内存系统的重排->最终执行的指令
单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
处理器在进行重排序时必须要考虑指令之间的数据依赖性
多线程环境中线程是交替执行的,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。加入了volatile之后,就可以保证有序性。
不保证原子性
验证不保证原子性代码:
public class volatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for(int j=1;j<=1000;j++){
myData.addPlusPlus();
myData.addMyAtomic();
}
},String.valueOf(i)).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\tAtomicInteger type,final number"+myData.atomicInteger);
}
}
class MyData{
volatile int number = 0;//没有使用volatile,则最后结果不会是20000
public void addT060(){
this.number =60;
}
public void addPlusPlus(){
number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addMyAtomic(){
atomicInteger.getAndIncrement();
}
}
如何解决?
1、加synchonized关键字;
分析:过重,不适合。
2、直接使用juc下AtomicInteger。
atomicInteger.getAndIncrement(); 解决了多线程环境下number++的问题;底层原理是CAS。
CAS原理:CAS原理描述
三、哪里有用过volatile?
单例模式DCL(Double Check Lock双端检锁机制),DCL机制不一定线程安全,原因是指令重排序的存在,加入volatile可以禁止指令重排。
原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。
Instance = new SingletonDemo();可以分为以下三步完成。
Memory = allocate(); //1.分配对象内存空间
Instance(memory); //2.初始化对象
Instance = memory;//3.设置instance指向刚分配的内存地址,此时instance!=null
2和3不存在依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变。
适用场景
仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它们。如果在验证正确性时需要对可见性进行复杂的判断,那么就不要使用volatile变量。
例如:检查某个状态标记以判断是否退出循环。
volatile boolean asleep;
while(!asleep){
countSomeSheep();
}
满足以下所有条件,才应该使用volatile变量:
- 对变量的写入操作不依赖变量的当前值,或者确保只有一个线程更新变量的值
- 该变量不会与其他状态变量一起纳入不变性条件中
- 在访问变量时不需要加锁
局限性
volatile不能保证递增操作(count++)的原子性,即不可保证原子性。