单例设计模式-Double Check
单例设计模式主要是为了保证只创建一个对象,其余时候需要复用的话就直接引用那个对象即可。简单来说,就是在整个应用中保证只有一个类的实例存在。
我们常用的单例模式有 饿汉式单例 和 饱汉式单例
饿汉式单例设计模式
package com.imodule.dataImport.study; /** * 饿汉式单例设计模式 * 1.定义一个静态私有成员变量 并初始值为当前类的实例化对象 * 2.私有化构造方法(防止被外部通过 new 构造方法创建对象) * 3.定义public的静态方法返回当前类的实例化对象 * @author S0111 * */ public class SingleTon1 { private static final SingleTon1 instance = new SingleTon1(); private SingleTon1() {} public static SingleTon1 getInstance() { return instance; } }
优点:实现起来简单,没有多线程同步问题。
缺点:当类Singletont被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。
饱汉式单例设计模式-初版(适合单线程访问)
package com.imodule.dataImport.study; /** * 饱汉-单例设计模式 * 1.定义一个静态的私有成员变量,初始值为null * 2.定义私有的构造方法(防止被外部通过 new 构造方法创建对象) * 3.定义一个静态的public获取对象的方法,里卖先判断对象是否为空 * 是: 通过构造方法创建对象再返回 * 否: 直接返回对象 * @author S0111 * */ public class SingleTon { private static SingleTon instance = null; private SingleTon(){} public static SingleTon getInstance(){ if(instance == null){ instance = new SingleTon(); } return instance; } }
优点:实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
缺点:在多线程环境中,这种实现方法会有问题,不能完全保证单例的状态。
在多线程访问的情况下以上单例是可能会失效的,因为创建对象分为三步
eg: instance = new SingleTon();
1.为单例对象在堆上分配内存 //mem = allocate();
2.创建SingleTon的实例对象 // ctorSingleton(instance);
3.将实例对象 instance 指向内存空间 (这一步instance才为非null) // instance = mem;
Java编译为字节码后,JVM 的即时编译器中存在指令重排序的优化 ,有可能进行指令重排序 ,最终的执行顺序可能是 1-2-3 也可能是 1-3-2(2与3互换顺序)
1.为单例对象在堆上分配内存 //mem = allocate();
3.将实例对象 instance 指向内存空间 (这一步instance才为非null) // instance = mem;
2.创建SingleTon的实例对象 // ctorSingleton(instance);
如果指令的执行顺序如上 1-3-2,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance(此时的instance为null),然后被调用使用,从而会报错。
这里的关键在于 —— 线程thread1对instance的写操作没有完成,线程thread2就执行了读操作。
饱汉式单例 多线程访问-方案一(在获取实例的方法上加锁 synchronized)
public class SingleTon { private static SingleTon instance = null; private SingleTon(){} //直接加锁 但是这样子效率有点低 public static synchronized SingleTon getInstance(){ if(instance == null){ instance = new SingleTon(); } return instance; } }
不推荐使用,效率太低了啦
饱汉式单例 多线程访问-方案二 也就是本文的重点啦 (Double-check)
1.使用volatile关键字修饰 instance,防止指令重排序
2.加上同步代码块 保证线程安全(比同步方法高效,因为同步方法每次获取对象都会执行,同步代码块只在第一次获取对象时会执行,后期会直接返回对象信息)
public class SingleTon { private static volatile SingleTon instance = null;// volatile 关键字修饰变量 防止指令重排序 private SingleTon(){} public static SingleTon getInstance(){ if(instance == null){ //同步代码块 只有在第一次获取对象的时候会执行到 ,第二次及以后访问时 instance变量均非null故不会往下执行了 直接返回啦 synchronized(SingleTon.class){ if(instance == null){ instance = new SingleTon(); } } } return instance; } }
推荐使用!!!
volatile关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。