设计模式之单例设计模式
设计模式之单例设计模式
单例模式的实现目标就是保证一个类有且仅有一个实例,当然这也是有前提的,就是由同一个ClassLoader加载的这个类有且仅有一个对象,如果这里类由不同的ClassLoader加载,则会产生多个对象。
(一) 单线程下的单例设计模式
(1)饿汉式
public class Singleton { private static final Singleton instance = new Singleton(); //构造函数私有化 private Singleton() { } public static Singleton getInstance() { return instance; } }
(2)懒汉式
public class Singleton { private static Singleton instance = null; //构造函数私有化 private Singleton() { } public static Singleton getInstance() { if(null == instance) { instance = new Singleton(); } return instance; } }
(二)多线程下的单例设计模式
在多线程环境下,饿汉式单例模式是线程安全的。但懒汉式单例模式getInstance()的if语句形成了一个if-then-act操作,它并不是一个原子操作,因此可能引发线程安全问题,创建多个对象,破坏单例性。
很容易让我们想到的是,采用同步(synchronized)的方式加锁,保证线程安全,如下所示:
/** * 基于简单加锁的单例模式实现 * @author Administrator * */ public class Singleton { private static Singleton instance = null; //构造函数私有化 private Singleton() { } public static Singleton getInstance() { synchronized(Singleton.class) { if(instance == null) { instance = new Singleton(); } } return instance; } }
这种方法的单例模式固然是线程安全的,但是每个线程在执行getInstance()方法时,都必须申请锁,增加了系统的开销,降低了效率,而我们需要的仅仅是第一次创建对象时加锁即可,之后所有线程可并发获得该对象。于是我们采用双重检查锁定的方式实现单例模式:
/** * 基于双重检查锁定的单例模式实现(错误代码) * @author Administrator * */ public class Singleton { private static Singleton instance = null; //构造函数私有化 private Singleton() { } public static Singleton getInstance() { if(null == instance) {//第一次判断用于判断对象是否被已经初始化,当对象已经初始化时,直接返回对象,避免锁的申请 synchronized(Singleton.class) { if(instance == null) {//第二次判断用来避免创建多个对象 instance = new Singleton();//语句① } } } return instance; } }
看似这种做法很巧妙解决了多线程环境下的单例模式的安全问题。其实不然,上述代码中的语句①可分解为三个子操作,分配对象所需的内存空间(子操作①)、初始化对象(子操作②)和将对象引用传入栈内的变量。想象一下这样一个场景,线程t1通过了第一次null == instance的判断,线程t1持有锁进行了第二次判断,此时线程t1进入了临界区,根据临界区重排序规则:临界区内的代码运行被重排序,因此,子操作③可能排在了子操作②之前,因此当线程t1执行到操作③时,由于此时操作②并没有执行,因此instance对象并没有初始化完成,但是instance以不为空,就在此时,线程t2执行了第一次null==instance判断,随然instance并未初始化完成,但以不是 null,所以直接返回给线程t2 instance对象,这样线程t2调用instance对象时就可能产生未知的错误。所以上述代码是不正确的。
需要将instance变量的采用volatile修饰,禁止其写操作与该操作之前的任何读、写操作进行重排序,因此,用volatile修饰instance相当于禁止了子操作②(对对象进行初始化的写操作)重拍到子操作③(对对象引用写入共享变量的子操作)之前,保证了线程读到的instance都是初始化完成的instance。
/** * 基于双重检查锁定的单例模式实现(正确实现) * @author Administrator * */ public class Singleton { private static volatile Singleton instance = null;//保障有序性 //构造函数私有化 private Singleton() { } public static Singleton getInstance() { if(null == instance) {//第一次判断用于判断对象是否被已经初始化,当对象已经初始化时,直接返回对象,避免锁的申请 synchronized(Singleton.class) { if(instance == null) {//第二次判断用来避免创建多个对象 instance = new Singleton(); } } } return instance; } }
考虑到双重检测锁定法实现上容易出错,我们选择另外一种可以延迟加载的单例模式:基于静态内部类的单例模式。
静态内部类只有在初次访问时才会触发虚拟机对该类进行初始化,因此SingletonInstanceHolder.INSTANCE会初始化静态内部类
/** * 基于静态内部类的单例模式实现 * @author Administrator * */ public class Singleton { //构造函数私有化 private Singleton() { } private static class SingletonInstanceHolder { private final static Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstanceHolder.INSTANCE; } }
也可通过枚举来实现线程安全的单例模式:
public enum Singleton { INSTANCE;//为单例对象 Singleton() { } public void doSomething() { } }