该系列文章已收录在公众号【Ccww技术博客】,原创技术文章早于博客推出
在深刻理解使用Volatile与Synchronized时,应该先理解明白Java内存模型 (Java Memory Model,JMM)java
Java内存(JMM)模型是在硬件内存模型基础上更高层的抽象,它屏蔽了各类硬件和操做系统对内存访问的差别性,从而实现让Java程序在各类平台下都能达到一致的并发效果。安全
JMM的内部工做机制多线程
JMM内部会有指令重排,而且会有af-if-serial跟happen-before的理念来保证指令的正确性并发
Java内存模型为了解决多线程环境下共享变量的一致性问题,包含三大特性,app
在理解了JMM的时,来说讲Volatile与Synchronized的使用,Volatile与Synchronized到底有什么做用呢?jvm
Volatile 的特性:性能
当写一个volatile变量时,JMM会把该线程对应的工做内存中的共享变量值更新后刷新到主内存,优化
当读取一个volatile变量时,JMM会把该线程对应的工做内存置为无效,线程会从主内存中读取共享变量。this
写操做:
spa
读操做:
JMM对volatile的禁止指令重排采用内存屏障插入策略:
在每一个volatile写操做的前面插入一个StoreStore屏障。在每一个volatile写操做的后面插入一个StoreLoad屏障
在每一个volatile读操做的后面插入一个LoadLoad屏障。在每一个volatile读操做的后面插入一个LoadStore屏障
Synchronized是Java中解决并发问题的一种最经常使用的方法,也是最简单的一种方法。Synchronized的做用主要有三个:
Synchronized总共有三种用法:
- 当synchronized做用在实例方法时,监视器锁(monitor)即是对象实例(this);
- 当synchronized做用在静态方法时,监视器锁(monitor)即是对象的Class实例,由于Class数据存在于永久代,所以静态方法锁至关于该类的一个全局锁;
- 当synchronized做用在某一个对象实例时,监视器锁(monitor)即是括号括起来的对象实例;
更加详细的解析看[Java并发之Synchronized]()
理解了Volatile与Synchronized后,那咱们来看看如何使用Volatile与Synchronized优化单例模式
先来看看通常模式的单例模式:
class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); // 建立实例 } return singleton; } }
可能出现问题:当有两个线程A和B,
if(singleton == null)
准备执行建立实例时,线程挂起,首先想到是那就在使用synchronized做用在静态方法:
public class Singleton { private static Singleton singleton; private Singleton(){} public static synchronized Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } }
虽然这样简单粗暴解决,但会致使这个方法比较效率低效,致使程序性能严重降低,那是否是还有其余更优的解决方案呢?
能够进一步优化建立了实例以后,线程再同步锁以前检验singleton非空就会直接返回对象引用,而不用每次都在同步代码块中进行非空验证,
若是只有synchronized前加一个singleton非空,就会出现第一种状况多个线程同时执行到条件判断语句时,会建立多个实例
所以须要在synchronized后加一个singleton非空,就不会出现会建立多个实例,
class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized(Singleton.class){ if(singleton == null) singleton = new Singleton(); } } return singleton; } }
这个优化方案虽然解决了只建立单个实例,因为存在着指令重排,会致使在多线程下也是不安全的(当发生了重排后,后续的线程发现singleton不是null而直接使用的时候,就会出现意料以外的问题。)。致使缘由singleton = new Singleton()
新建对象会经历三个步骤:
因为重排序的缘故,步骤二、3可能会发生重排序,其过程以下:
那么问题找到了,那怎么去解决呢?那就禁止不容许初始化阶段步骤2 、3发生重排序,恰好Volatile 禁止指令重排,从而使得双重检测真正发挥做用。
public class Singleton { //经过volatile关键字来确保安全 private volatile static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
最终咱们这个完美的双重检测单例模式出来了
各位看官还能够吗?喜欢的话,动动手指点个💗,点个关注呗!!谢谢支持!
欢迎关注公众号【Ccww技术博客】,原创技术文章第一时间推出