单例模式(Singleton Pattern)

506 阅读5分钟

在设计模式中按照不同的处理方式共包含三大类:创建型模式、结构型模式和行为模式。

创建型模式
工厂方法模式(Factory Method Pattern)
抽象工厂模式(Abstract Factory Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
单例模式(Singleton Pattern)

定义

它的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。单例模式可以说是整个设计中最简单的模式之一,因为在编程开发中经常会遇到这样一种场景,那就是需要保证一个类只有一个实例哪怕多线程同时访问,综上以及我们平常的开发中,可以总结一条经验,单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升提升整体的代码的性能。

7种单例模式的实现方式

1.懒汉模式(线程不安全)

/**
 * 懒汉式
 * 需要使用时再进行创建 线程不安全 不浪费资源
 */
private static Singleton singleton;

public static Singleton getInstance() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

优点

属于懒加载的方式不会浪费资源。 缺点 在并发环境下。有可能会产生多个单例对象。线程不安全

2.懒汉式加同步锁(线程安全)


/**
 * 懒汉式 加同步锁
 * 线程安全 不浪费资源 效率低
 * 描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作 
 * 但是,效率很低,大部分情况下不需要同步。
 * 优点:第一次调用才初始化,避免内存浪费。
 * 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
 * getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
 */
private static Singleton singleton2;

public static synchronized Singleton getInstance2() {
    if (singleton2 == null) {
        singleton2 = new Singleton();
    }
    return singleton2;
}

优点

  • 线程安全
  • 属于懒加载的方式不会浪费资源。 缺点 效率低。大部分情况下不需要同步锁。

3.饿汉式(线程安全)

/**
 * 线程安全 浪费资源
 * 描述:这种方式比较常用,但容易产生垃圾对象。
 * 优点:没有加锁,执行效率会提高。
 * 缺点:类加载时就初始化,浪费内存。
 * 它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,
 * 虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,
 * 这时候初始化 instance 显然没有达到 lazy loading 的效果。
 */
private static Singleton singleton3 = new Singleton();

public static Singleton getInstance3() {
    return singleton3;
}

优点

线程安全,基于类加载机制在对象加载的时候就已经创建好了单例对象。在整个JVM环境中只存在一个对象 缺点 容易产生垃圾对象

4.双重锁校验(Double Check)(线程安全)

/**
 * 双重锁校验
 * double-check-locking
 * JDK 版本:JDK1.5 起
 * 是否 Lazy 初始化:是
 * 是否多线程安全:是
 * 实现难度:较复杂
 *
 * 描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
 * getInstance() 的性能对应用程序很关键。
 */
private static Singleton singleton4;
public static Singleton getInstance4() {
    if (singleton4 == null) {
        synchronized (Singleton.class) {
            if (singleton4 == null) {
                singleton4 = new Singleton();
            }
        }
    }
    return singleton4;
}

优点

  • 懒加载
  • 线程安全 缺点 效率低。由于加入了同步锁的方式使得效率低下。

5.静态内部类(线程安全)

/**
 * 登记式/静态内部类
 * 是否 Lazy 初始化:是
 * 是否多线程安全:是
 * 实现难度:一般
 * 描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
 * 这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果)
 * 而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化
 * instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,
 * 那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。
 * */
private static class SingletonHolder{
    private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance5(){
    return SingletonHolder.INSTANCE;
}

优点

  • classloader加载静态内部类保证在被调用时才对单例类进行加载。不存在浪费资源
  • 线程安全 缺点
  • 没有什么明显的缺点。非要说的话就是使结构变得复杂了一点。但这种模式完全符合所有的情况。但为了实用。使用最多的还是静态类的方式去创建单例对象。

6.静态类(线程安全)

public static Singleton singleton6 = new Singleton();

优点

简单方便。非常适合在开发过程中使用 缺点 不是懒加载。

7.枚举(线程安全)

public enum Singletons{
    INSTANCE;
    Singletons() {
    }
}
public static Singletons getInstance7(){
    return Singletons.INSTANCE;
}

Effective Java作者推荐的枚举单例(线程安全)

  • Effective Java 作者推荐使用枚举的方式解决单例模式,此种方式可能是平时最少用到的。
  • 这种方式解决了最主要的;线程安全、自由串行化、单一实例。