单例模式初学的时候沉浸于各种高深莫测的写法和眼花缭乱的多变种写法,似乎也没思考过这玩意到底用在啥地方。在工作中使用的时候就会发现,所有可以使用单例模式的类都有一个共性 - 无状态。简单理解就是,一个类在一个应用中创建10000个实例都是一样,没啥变化或者依赖这个类的实例都不希望它有变化。比较典型的案例就是保存配置文件信息的类了
所以可以化繁为简,既减少了内存消耗,也减少GC次数,完美
最简单,最原始的写法就是根据单例模式的定义构造的写法
public class Singleton { //一个静态的实例 private static Singleton singleton; //私有化构造函数 private Singleton(){} //给出一个公共的静态方法返回一个单一实例 public static Singleton getInstance(){ if (singleton == null) { singleton = new Singleton(); } return singleton; } }
这是在不考虑并发访问的情况下标准的单例模式的构造方式,这种方式通过几个地方来限制了我们取到的实例是唯一的。
1.静态实例,带有static关键字的属性在每一个类中都是唯一的。
2.限制客户端随意创造实例,即私有化构造方法,此为保证单例的最重要的一步。
3.给一个公共的获取实例的静态方法,注意,是静态的方法,因为这个方法是在我们未获取到实例的时候就要提供给客户端调用的,所以如果是非静态的话,那就变成一个矛盾体了,因为非静态的方法必须要拥有实例才可以调用。
4.判断只有持有的静态实例为null时才调用构造方法创造一个实例,否则就直接返回。
但是在并发的情况下该方法就无法保证单例。例如线程A先执行,越过singleton == null的校验之后换B线程执行,线程B执行方法并创建一个B实例,B线程执行结束之后A线程继续执行,A线程从暂停处开始执行又会创建一个A实例。两个实例明显不属于单例
为了解决并发的问题,脑袋一拍可以想到一个最简单的方法,加锁
public class BadSynchronizedSingleton { //一个静态的实例 private static BadSynchronizedSingleton synchronizedSingleton; //私有化构造函数 private BadSynchronizedSingleton(){} //给出一个公共的静态方法返回一个单一实例 public synchronized static BadSynchronizedSingleton getInstance(){ if (synchronizedSingleton == null) { synchronizedSingleton = new BadSynchronizedSingleton(); } return synchronizedSingleton; } }
上面的做法很简单,就是将整个获取实例的方法同步,这样在一个线程访问这个方法时,其它所有的线程都要处于挂起等待状态,倒是避免了刚才同步访问创造出多个实例的危险,但是,这样的设计实在是糟糕透了,这样会造成很多无谓的等待。
这种写法会发现同步的代码太多,有些无需同步的代码也同步了。其实我们同步的地方只是需要发生在单例的实例还未创建的时候,在实例创建以后,获取实例的方法就没必要再进行同步控制了,所以我们将上面的示例改为很多教科书中标准的单例模式版本,也称为双重加锁
public class SynchronizedSingleton { //一个静态的实例 private static SynchronizedSingleton synchronizedSingleton; //私有化构造函数 private SynchronizedSingleton(){} //给出一个公共的静态方法返回一个单一实例 public static SynchronizedSingleton getInstance(){ if (synchronizedSingleton == null) { synchronized (SynchronizedSingleton.class) { if (synchronizedSingleton == null) { synchronizedSingleton = new SynchronizedSingleton(); } } } return synchronizedSingleton; } }
这种做法与上面那种最无脑的同步做法相比就要好很多了,因为我们只是在当前实例为null,也就是实例还未创建时才进行同步,否则就直接返回,这样就节省了很多无谓的线程等待时间,值得注意的是在同步块中,我们再次判断了synchronizedSingleton是否为null,解释下为什么要这样做。
假设我们去掉同步块中的是否为null的判断,有这样一种情况,假设A线程和B线程都在同步块外面判断了synchronizedSingleton为null,结果A线程首先获得了线程锁,进入了同步块,然后A线程会创造一个实例,此时synchronizedSingleton已经被赋予了实例,A线程退出同步块,直接返回了第一个创造的实例,此时B线程获得线程锁,也进入同步块,此时A线程其实已经创造好了实例,B线程正常情况应该直接返回的,但是因为同步块里没有判断是否为null,直接就是一条创建实例的语句,所以B线程也会创造一个实例返回,此时就造成创造了多个实例的情况。
上述写法都是在语法特性上进行单例模式的构建,其实在语言特性上,java也有天然支持单例的特性
public class Singleton { private Singleton(){} public static Singleton getInstance(){ return SingletonInstance.instance; } private static class SingletonInstance{ static Singleton instance = new Singleton(); } }
1.Singleton最多只有一个实例,在不考虑反射强行突破访问限制的情况下。
2.保证了并发访问的情况下,不会发生由于并发而产生多个实例。
3.保证了并发访问的情况下,不会由于初始化动作未完全完成而造成使用了尚未正确初始化的实例。
枚举类型天然单例
public enum Singleton { INSTANCE; // 私有构造方法 private Singleton() {} public static Singleton getInstance() { return INSTANCE; } }