单例模式(Singleton Pattern)
定义:确保一个类有且仅有一个实例,而且自行实例化并向整个系统提供这个实例
类型:创建型模式
单例的几种实现方式:
原始的单例模式的构造方式
public class Singleton { //静态实例 private static Singleton singleton; //私有化构造函数 private Singleton() {} //公共静态方法返回单一实例 public static Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } }
这是在不考虑并发访问时情况下标准的单例模式的构造方式,而在并发情况下非线程安全。
这里提供几种解决方案:
第一、使用synchronized来处理,即将getInstance()方法变成同步方法
public class Singleton { //静态实例 private static Singleton singleton; //私有化构造函数 private Singleton() {} //公共静态方法返回单一实例 public synchronized static Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } }
上面的做法很简单,就是将整个获取实例的方法同步,这样在一个线程访问这个方法时,其它所有的线程都要处于挂起等待状态,这样虽然避免了同步访问创造出多个实例的可能,但这样会造成很多无谓的等待。
其实同步的地方只是需要发生在单例的实例还未创建的时候,在实例创建以后,获取实例的方法就没必要再进行同步控制了,所以我们将上面的示例做进一步改进,也称为双重加锁。
第二、用“双重检查加锁”,在getInstance()中减少使用同步
public 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; } }
这种做法与相比第一种要好很多,因为我们只是在当前实例为null,也就是实例还未创建时才进行同步,否则就直接返回,这样就节省了很多无谓的线程等待时间,值得注意的是在同步块中,我们再次判断了singleton是否为null,解释下为什么要这样做。
假设我们去掉同步块中的是否为null的判断,有这样一种情况,假设A线程和B线程都在同步块外面判断了singleton为null,结果A线程首先获得了线程锁,进入了同步块,然后A线程会创造一个实例,此时singleton已经被赋予了实例,A线程退出同步块,直接返回了第一个创造的实例,此时B线程获得线程锁,也进入同步块,此时A线程其实已经创造好了实例,B线程正常情况应该直接返回的,但是因为同步块里没有判断是否为null,直接就是一条创建实例的语句,所以B线程也会创造一个实例返回,此时就造成创造了多个实例的情况。
经过分析,上述双重加锁的示例看起来是没有问题了,但如果深入到JVM中去探索上面这段代码,它就有可能(注意,只是有可能)是有问题的。因为虚拟机在执行创建实例的这一步操作的时候,其实是分了好几步去进行的,也就是说创建一个新的对象并非是原子性操作。在有些JVM中上述做法是没有问题的,但是有些情况下是会造成莫名的错误。所以我们在语言级别无法完全避免错误的发生,我们只有将该任务交给JVM,因此有了第三种单例实现方式。
第三、使用静态的内部类作为单例
public class Singleton { //私有化构造函数 private Singleton(){} //公共静态方法返回单一实例 public static Singleton getInstance(){ return SingletonInstance.instance; } private static class SingletonInstance{ static Singleton instance = new Singleton(); } }