单例模式

66 阅读4分钟

简述

单例模式是设计模式中最简单的模式之一,其主要目的是某个类的对象在系统中只存在一个实例。 比如window系统资源管理器,不管启动多少次,系统始终只有一个管理器对象。

UML类图

single.png

要点

单例模式有3个要点:

  • 单例类有且只有一个实例
  • 单例类必须自行创建自己的唯一实例
  • 单例类必须给其他对象提供这个实例

从具体实现角度可以分为以下3点:

  • 提供一个private 的构造函数
  • 提供一个该类的 static private 对象
  • 提供一个static public 函数,用于获取本身的静态私有对象

另外还有如下部分需要注意:

  • 线程安全。双检锁-DCL,即double checked locking
  • 资源释放

饿汉模式

单例模式也有好几种实现方式。比如饿汉模式,实现代码如下:

public class Singleton {
    private static Singleton mInstance = new Singleton();
    
    private Singleton() {
    }
    
    public static getInstance() {
        return mInstance;
    }
}

饿汉模式的实现要点是:在声明全局静态实例变量的时候直接构造一个单例对象。因为是静态变量,在类加载的时候就会初始化。

优点:没有多线程引用问题 缺点:没有使用的时候也会有对象创建,可能造成系统资源开销。

懒汉模式

懒汉模式是将对象的构建延后执行,在需要的时候才创建。 初始版本如下:

public class Singleton {
    private static Singleton mInstance = null;
    
    private Singleton() {
    }
    
    public synchronized static getInstance() {
        if (mInstance == null) {
            mInstance = new Singleton();
        }
        return mInstance;
    }
}

因为涉及到多线程安全问题,所以这里加上synchronized同步关键字是必要的。但是这里会有性能问题,在高并发场景下多线程访问的时候必定存在线程锁定情况。这里我们可以将锁范围进行缩小,只需要锁定构建对象的代码,代码如下:

public class Singleton {
    private static Singleton mInstance = null;
    
    private Singleton() {
    }
    
    public static getInstance() {
        if (mInstance == null) {
            synchronized(Singleton.class) {
                mInstance = new Singleton();
            }
        }
        return mInstance;
    }
}

问题貌似得到解决,实际上并没有。因为当两个线程同时执行完mInstance == null后,存在2个线程都创建Singleton对象的可能,这里就不能保证唯一性了。 优化方案如下:

public class Singleton {
    private static Singleton mInstance = null;
    
    private Singleton() {
    }
    
    public static getInstance() {
        if (mInstance == null) {
            synchronized(Singleton.class) {
                if (mInstance == null) {
                    mInstance = new Singleton();
                }
            }
        }
        return mInstance;
    }
}

在同步锁内再加一层校验机制,这样就能防止创建多个实例对象了。

饿汉与懒汉比较

性能: 饿汉优于懒汉。因为懒汉涉及到多线程锁的等待,会有性能的开销 资源: 懒汉优于饿汉。因为懒汉是到使用时才初始化,而饿汉却是在类加载的时候就创建了。

一种更好的解决方案

我们在单例类中新增一个静态内部类可以同时保证饿汉和懒汉这两种实现方案的优点。

public class Singleton {    
    private Singleton() {
    }
    
    private static class HolderClass {
        private final static Singleton mInstance = new Singleton();
    }
    
    public static getInstance() {
        return HolderClass.mInstance;
    }
}
  • 因为静态单例变量没有作为Singleton的成员变量,所以在Singleton加载的时候,不会创建Singleton对象。
  • 在调用getInstance()的时候,会加载HolderClass内部类,java虚拟机会保证静态变量mInstance线程安全的被创建。

由于此处没有任何线程锁定,所以性能是ok的, 同时也能保证使用时才初始化。 缺点:不是所有的面向对象编程语言都支持。

总结

优点:

  1. 单例模式提供了对唯一实例的受控访问。
  2. 因为系统只存在一个实例,所以可以节省资源
  3. 允许可变数目的实例。我们可以对单例进行扩展,创建指定个数的对象实例。

缺点:

  1. 因为没有抽象层,所以单例类的扩展性有限。
  2. 单例类的职责过重,在一定程度上违背了单一职责原则。因为其既提供了业务方法,又提供了创建对象的方法(工厂方法),将对象的创建和对象本身的功能耦和在一起。
  3. 在java等支持垃圾回收机制。可能存在因为长时间不使用,被系统认为它是垃圾,会自动销毁并回收资源,这将导致共享单例中的状态丢失。