ps:疑问都是用红色的字体标识出来,最后引出知识点。。。
第一个疑问:volatile到底是什么?
volatile是Java虚拟机提供的轻量级的同步机制。
特性:能保证可见性和非原子性和禁止指令重排。 适用于:多线程环境下的单次操作(单次读或者单次写)
volatile的特性又引发出来了一个疑问,volatile不能保证原子性,那如何解决volatile的原子性呢?
两种方式:1.加synchronized修饰;2.使用我们juc(java.util.concurrent并发包)下的AtomicInteger
在了解volatile的使用场景之前要懂得一个知识什么是JMM,JMM就是Java虚拟模型,画个图解释一下
JMM有几大特性:可见性;原子性和有序性。
T1线程和T2线程同时去访问主物理内存,它得真实值是5,T1和T2把主物理内存得真实值5得副本拷贝到自己得工作线程中。若T1需要修改,则在自己的工作内存中进行修改,假如由5变为3,变更为3后需要同步到主物理内存中,此时主物理内存的真实值为3.此时T1变更主物理内存中的值,T2线程可见,然后T2就再次获取主物理内存的真实值,此时已经是3了,然后拷贝3的副本到T2的工作内存中,进行变更,回写。这就是简单的对JMM的口语化的描述。
原子性:线程不可分割,完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或被分割,需要整体完整。要么同时成功,要么同时失败。
有序性:为了保证有序需要禁止指令重排。
禁止指令重排小总结:多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。所以用volatile修饰变量禁止指令重排。
以上是JMM基本特性和认识,
要想线程安全获得保证:
1.工作内存和主内存同步延迟现象导致的可见性问题
可以使用synchronized或volatile关键字解决,他们都可以使一个线程修改后的变量立即对其他线程可见。
2.对于指令重排导致的可见性问题和有序性问题
可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。
那继续之前的问题,volatile的适用场景,那些地方用到了呢?
单例模式DCL代码,也即双端检锁机制,Double Check Lock
private static volatile SingletonDemo instance = null;//用volatile修饰禁止指令重排
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo()");
}
//DCL模式 Double check Lock 双端检锁机制..就是加锁得前后都进行判断
public static SingletonDemo getInstance(){
if (instance == null){
synchronized (SingletonDemo.class){//新建实例的上下都要判断一遍
if(instance == null){
instance = new SingletonDemo();
}
}
}
return instance;
}
那大家看了上面的实现单例的代码,又会有另一个疑问?synchronized修饰之后,线程就安全了,这样就能实现了单例模式,为什么还要用volatile修饰呢?
但是大家忽略了一点:双端检索机制不一定线程安全,原因是有指令重排序的存在,加入volatile可以禁止指令重排。
单例模式volatile分析:
原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。
instance = new SingletonDemo();可以分为以下3步完成(伪代码)
memory = allocate();//1.分配对象内存空间
instance(memory);//2.初始化对象
instance = memory;//3.设置instance指向刚分配的内存地址,此时instance!=null
步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因为这种重排优化时允许的。
memory = allocate();//1.分配对象内存空间
instance = memory;//3.设置instance指向刚分配的内存地址,此时instance!=null,但是对象还灭有初始化完成!
instance(memory);//2.初始化对象
但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。
所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题
总结以上,就是用volatile修饰,实现禁止指令重排,正确的实例化出来一个单例。Private static volatile SingletonDemo insatance=null;
由于上面的问题,volatile的其中一条特性是非原子性,解决非原子性其中一条是用juc下的AtomicInteger解决,那AtomicInteger又是什么呢,原理是什么呢?这就引出了CAS
Atomic的底层实现原理是CAS,见下一篇解释https://blog.csdn.net/qq_30546099/article/details/112614484
ps:有什么疑问可以留言讨论。