单例模式

一、什么是单例模式

1. 只能保证类只有一个实例。

普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。

2. 为该实例提供一个全局访问节点。

也就是对外部提供一个统一的获取实例的入口

二、怎么实现单例模式

1. 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
2. 新建一个静态构建方法作。该方法用来调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

下图是我从网上“偷”来的一张单例模式类图,从图中很明显的可以看出单例模式的结构模式

三、适用场景

1. 对于程序中的某个类全局共用一个实例

单例模式并不等同于全局变量,因为它只保证类存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

四、伪代码

1. Eager initialization

这种模式下单例类的实例是在类加载时创建的,这是创建单例类的最简单方法,但它有一个缺点,即即使客户端应用程序可能不会使用它,也会创建实例。

public class EagerInitialization {
    public static final EagerInitialization eagerInitialization= new EagerInitialization();

    private EagerInitialization() {
    }

    public static EagerInitialization getInstance(){
        return eagerInitialization;
    }
}
2. Static block initialization

静态块初始化实现类似于预先初始化,不同之处在于类的实例是在静态块中创建的,

public class StaticBlockSingleton {
    private static StaticBlockSingleton staticBlockSingleton;

    private StaticBlockSingleton() {
    }

    static{
        try{
            staticBlockSingleton = new StaticBlockSingleton();
        }catch(Exception e){
            throw new RuntimeException("creating singleton instance exception");
        }
    }

    public static StaticBlockSingleton getInstance(){
        return staticBlockSingleton;
    }
}
3. Lazy Initialization(懒汉式)

实现单例模式的延迟初始化方法在全局访问方法中创建实例。并不会类加载时就创建实例,而时在客户端第一次访问时才会初始化。

public class LazyInitializedSingleton {
    public static LazyInitializedSingleton lazyInitializedSingleton;

    private LazyInitializedSingleton() {
    }

    public static LazyInitializedSingleton getInstance(){
        if(lazyInitializedSingleton == null){
            lazyInitializedSingleton =  new LazyInitializedSingleton();
        }
        return lazyInitializedSingleton;
    }
}

在多线程环境下,上述生成单例模式是不安全的,下面会讲述几种线程安全的创建方式。

4. Thread Safe Singleton

最简单的是使全局访问方法同步,以便一次只有一个线程可以执行此方法。

public class ThreadSafeSingleton {
    public static ThreadSafeSingleton threadSafeSingleton;

    private ThreadSafeSingleton() {
    }

    public static synchronized ThreadSafeSingleton getInstance(){
        if(threadSafeSingleton == null){
            threadSafeSingleton =  new ThreadSafeSingleton();
        }
        return threadSafeSingleton;
    }
}

其实并不需要每次获取实例都进行锁,为了每次都避免这种额外的开销,还可以使用下面都双重校验锁都方式

public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
    if(threadSafeSingleton == null){
        synchronized (ThreadSafeSingleton.class) {
             if(threadSafeSingleton == null){
                 threadSafeSingleton = new ThreadSafeSingleton();
             }
         }
    }
    return threadSafeSingleton;
 }
5. Bill Pugh Singleton Implementation(静态内部类)

当加载单例类时,SingletonHelper类不会加载到内存中,只有当有人调用getInstance方法时,才会加载这个类并创建单例类实例。

public class BillPughSingleton {

    private BillPughSingleton() {
    }

    private static class SingletonHelper{
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
}
6. Using Reflection to destroy Singleton Pattern(反射)

反射会破坏单例模式,下面例子生成的hashcode是不同的,感兴趣的可以试试。

 public static void main(String[] args) {
        EagerInitialization instanceOne = EagerInitialization.getInstance();
        EagerInitialization instanceTwo = null;
        try {
            Constructor[] constructors = EagerInitialization.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                constructor.setAccessible(true);
                instanceTwo = (EagerInitialization) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(instanceOne.hashCode());
        System.out.println(instanceTwo.hashCode());
    }
7. Enum Singleton(枚举)

Java 确保任何枚举值在 Java 程序中仅被实例化一次。由于Java 枚举值是全局可访问的,因此单例也是如此。缺点是枚举类型有点不灵活;例如,它不允许延迟初始化。

public enum EnumSingleton {
    INSTANCE;

    public static void doSomething(){
        //do something
    }
}

一条小咸鱼