前言:首先对比一下单例模式-懒汉式和单例模式_DCL的区别,然后说明单例模式_DCL的静态成员变量为什么要用volatile变量修饰
单例模式_懒汉式
public class Singleton3 {
//单例懒汉式
//1.私有化构造器
private Singleton3() {
System.out.println("单例懒汉式构造器被私有化");
}
//2.创建一个静态变量先不去初始化实例
public static Singleton3 INSTANCE = null;
//3.getInstance方法来得到这个单例对象
public static Singleton3 getInstance(){
if (INSTANCE == null){
INSTANCE = new Singleton3();
}
return INSTANCE;
}
}
但是可以发现一个问题:当有多个线程需要用这个类,如果不加任何安全保护,那么多个线程都可以调用这个getInstance方法,在第一次调用的时候,赋值还未完成,多个线程可能同时进入而创建了多个对象实例.
解决办法:在getInstance方法上加上synchronized来确保线程安全
public static synchronized Singleton3 getInstance(){
if (INSTANCE == null){
INSTANCE = new Singleton3();
}
return INSTANCE;
}
但是当创建完这个对象实例后,我们其实就不用synchronized给getInstance加锁来确保线程安全了,反而会影响效率
这时我们就可以使用:
单例懒汉式_可以使用DCL方式来解决synchronized的性能问题–>DoubleCheckLocked双重检查锁的方式
单例模式_DCL懒汉式
//单例懒汉式_DCL
//1.私有化构造器
private Singleton4() {
System.out.println("单例懒汉式DCL构造器被私有化");
}
//2.创建一个静态变量先不去初始化实例
public static volatile Singleton4 INSTANCE = null;
//3.getInstance方法来得到这个单例对象
public static Singleton4 getInstance(){
if (INSTANCE ==null){
synchronized(Singleton4.class){
if (INSTANCE == null){
INSTANCE = new Singleton4();
}
}
}
return INSTANCE;
}
解读:先去判断INSTANCE是否为Null,如果为null则去让一个线程获得锁 继续判断是否为null,如果为null则去创建对象,否则直接返回这个对象。这样的话如果创建对象后,INSTANCE!=null就不会进入到synchronized中去,则不会影响性能.
如果不加第二个判断条件则会导致还是无法预防多线程而破坏单例.
注意:使用单例懒汉式DCL需要将Singleton4这个对象 用volatile来保证有序性
那么单例懒汉式DCL为什么要用volatile来保证有序性呢?
使用javap -v -p -c Singleton4.class反编译来看一下底层:
这里的new指的就是创建对象,即为对象分配一个空间
invokespecial 即调用构造方法来初始化成员变量
putstatic 就是给静态变量赋值
cpu可能会指令的执行次序进行优化,如果指令之间没有什么因果关系,那么cpu就可能会调换他们的执行次序
而putstatic和invokespecial其实都是一个赋值操作,没有因果关系,则cpu可能会调换他们的次序
单线程则不会造成什么影响,在多线程时则会可能造成静态变量中的成员变量因未经构造方法而导致变量初始化失败
画图来理解一下
首先:
蓝色代表—线程1
红色代表—线程2
例如:正常线程1执行顺序为
1.new Singleton4 创建对象,开辟内存空间
2.Singleton4(),调用构造方法,初始化成员变量
3.INSTANCE =对象,为静态变量赋值.
由于2,3间没有因果关系,cpu可能会对指令的执行次序进行优化,来调换他们的执行次序
即执行次序变为了 1 -> 3 -> 2
而线程2由于拿不到锁,但是可以执行 判断条件然后返回 INSTANCE
这时由于执行次序变为1->3->2
在1->3后,线程2执行了判断INSTANCE !=Null直接返回INSTANCE,则会导致线程1未执行完构造方法而直接返回,则返回的这个对象就会没有初始化得来的成员变量,产生各种问题
但是给 Singleton4加上volatile修饰后,相当于在赋值语句之后加上一个内存屏障,这个内存屏障的作用就是,阻止之前的一些赋值操作,越过屏障跑到他之后去,简单的说就是为了防止指令的重排序.
即
总结一下:volatile修饰就是为了保证代码或指令的有序性,防止重新排列。