设计模式之单例

设计模式之单例

定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。html

简单来讲,也就是须要保证一个类在整个应用程序的生命周期内,只能存在一个实例(没有也行)。为了达成这个目标,应该要作到如下几点:java

  1. 私有的构造器,若是是构造器被声明为public的,则没法控制其实例化;
  2. 一个获取实例的公开方法,也就是定义中所说的『提供一个访问它的全局访问点』;
  3. 一个用来保持此惟一实例的静态变量。

实现方法

首先,咱们按照前面说的三点,写了如下代码:设计模式

public class Singleton {

    private Singleton instance;

    private Singleton(){}

    public Singleton GetInstance(){
        return instance;
    }
}

这样,显然是有问题的。安全

一是获取实例的公开方法getInstance,只是生命成public的,外部在没有Singleton的实例的状况下仍是不能调用,就成了一个悖论了。因此须要把GetInstance方法声明为静态的。一样,因为须要被静态方法调用,同时还要用来保持惟一的实例,instance也须要声明为静态的。多线程

二就是还缺乏了对instance变量的初始化,即对构造器的调用。咱们既能够再声明instance是就进行初始化,也能够在静态代码段中对它进行初始化。并发

因而乎,就有了下面两个版本的实现:oracle

方法一:饿汉一

public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton(){}

    public static Singleton GetInstance(){
        return instance;
    }
}

方法二:饿汉二

public class Singleton {

    private static Singleton instance;
    
    static {
        instance = new Singleton();
    }

    private Singleton(){}

    public static Singleton GetInstance(){
        return instance;
    }
}

因为这两种方法没有实质上的区别,都是在类被加载的时候就进行了实例的初始化(因此被称为饿汉式)。这也是饿汉式的下的两个特色:性能

  1. 线程安全,由于在类加载时已经完成来实例化;
  2. 性能低,实例化后的对象在应用的声明周期中未必就会被使用,因此可能会产生计算的浪费。

方法三:懒汉

因为饿汉式在类加载时就完成了实例化,致使了可能存在性能浪费,因此咱们就考虑看看能不能在类被使用时才被实例化呢。若是据说过『懒加载』这个的词话,应该就会以为这很easy了,在以前的基础上,很轻松就写出了下面代码(懒汉式的单例)。优化

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}

    public static LazySingleton GetInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

乍一看,已是能够了,至少在单线程下已经没问题了。可是在多线程的场景下呢,很容易就会产生A、B两个线程同时调用GetInstance方法,A线程先判断instance为null,准备进行实例化,但在实例化以前B线程也进行了instance为null的判断,最终结果是两个线程分别调用了一次私有构造器进行实例化,第二次实例化的结果会将第一次的覆盖掉。线程

因此懒汉式有如下特色:

  1. 懒加载实现,第一次调用时实例化,不调用则不实例化,故计算效率高;
  2. 线程不安全,上面已经详细描述来是如何产生线程冲突的。

方法四:懒汉(线程安全)

为了解决上面方法的缺陷,也就是所谓的线程不安全,咱们找到了synchronized这个关键字,也只加了这个关键字,获得下面的代码。

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}

    public static synchronized LazySingleton GetInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

与前一种方法相比,在实现上只是在getInstance方法上增长了synchronized关键字,使得GetInstance方法同步,同一时间只能有一个线程执行这个方法,那咱们以前说的线程不安全的问题显然就不在了,这样是否是就完美来呢?世界没那么美好,咱们又引入了新的问题。

因为是在整个GetInstance方法上加锁(同步),可是由于实际上只须要进行一次实例化(也只容许进行一次),因此绝大多数场景下是不须要同步的,因此在并发场景下会致使效率下降,至关于多车道在这里并成单车道了。

方法五:懒汉(双重检查锁定)

既然还有问题,那咱们就来继续进行优化。上面的方法由于把整个GetInstance方法设置为synchronized,因此致使多线程在这里受阻,那咱们把同步的范围缩小一点儿,看看状况会不会好一些。

public class LazySingleton {
    private static volatile LazySingleton instance;
    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized(LazySingleton.class){
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

优化以后,把锁定范围进行了收缩,只在须要进行初始化实例时才进行同步,以后就再也不进行同步。

这样咱们就获得了一种效率较高,而且线程安全的单例模式的构造方法。

PS. 若是有仔细看代码,您或许会发现咱们在声明instance变量的时候,用了一个volatile关键字,若是须要一些解释的话,能够参考Java中的volatile在使用双层检查实现单例模式的解读,后面我也可能来单独说一下这个。

第六种:静态内部类

还有一种使用静态内部类来实现的单例,也被各类推荐。由于内部静态类是要在有引用了之后才会装载到内存的,这样就一样实现了懒加载;同时,静态内部类的静态变量的初始化,也是在被加载时进行的初始化,自然的完成来对进程安全的控制。

public class InnerStaticClassSingleton {
    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static InnerStaticClassSingleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

To Be Continued

相关文章
相关标签/搜索