Skip to content

单例模式

亦称: 单件模式、Singleton

1. 简介

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

2. 模拟场景

单例模式同时解决了两个问题, 所以违反了单一职责原则:

  1. 保证一个类只有一个实例。为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件)的访问权限。
    它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。
    注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。
  2. 为该实例提供一个全局访问节点。还记得你 (好吧, 其实是我自己)用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。 和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。但是它可以保护该实例不被其他代码覆盖。 还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

3. 解决方案

所有单例的实现都包含以下两个相同的步骤:
将默认构造函数设为私有, 防止其他对象使用单例类的new运算符。 新建一个静态构建方法作为构造函数。该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象。
如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。无论何时调用该方法, 它总是会返回相同的对象。

总结

单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
单例拥有与全局变量相同的优缺点。尽管它们非常有用, 但却会破坏代码的模块化特性。
在某些其他上下文中, 你不能使用依赖于单例的类。你也将必须使用单例类。绝大多数情况下, 该限制会在创建单元测试时出现。

4. 真实世界类比

政府是单例模式的一个很好的示例。一个国家只有一个官方政府。不管组成政府的每个人的身份是什么, ​ “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。

5. Java中的例子

  • java.lang.Runtime#getRuntime()
  • java.awt.Desktop#getDesktop()
  • java.lang.System#getSecurityManager()
  • Spring通过ConcurrentHashMap实现单例AbstractBeanFactory#getBean()

6. 单例模式的实现

java
public class Singleton_01 {
    private static Singleton_01 instance;

    private Singleton_01() {
    }

    public static Singleton_01 getInstance() {
        if (null != instance) return instance;
        instance = new Singleton_01();
        return instance;
    }
}
java
/**
 此种模式虽然是安全的,但由于把锁加到⽅法上后,所有的访问都因需要锁占⽤导致资源的浪费。
 如果不是特殊情况下,不建议此种⽅式实现单例模式。
 */
public class Singleton_02 {
    private static Singleton_02 instance;

    private Singleton_02() {
    }

    public static synchronized Singleton_02 getInstance() {
        if (null != instance) return instance;
        instance = new Singleton_02();
        return instance;
    }
}
java
/**
 双重锁的⽅式是⽅法级锁的优化,减少了部分获取实例的耗时。
 同时这种⽅式也满⾜了懒加载。
 */
public class Singleton_06 {
    private static Singleton_06 instance;
    private Singleton_06() {
    }
    public static Singleton_06 getInstance(){
        if(null != instance) return instance;
        synchronized (Singleton_06.class){
            if (null == instance){
                instance = new Singleton_06();
            }
        }
        return instance;
    }
}
java
/**
 此种⽅式在程序启动的时候直接运⾏加载,后续有外部需要使⽤的时候获取即可。
 */
public class Singleton_03 {
    private static Singleton_03 instance = new Singleton_03();
    private Singleton_03() {
    }
    public static Singleton_03 getInstance() {
        return instance;
    }
}
java
/**
 使⽤类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,
 同时不会因为加锁的⽅式耗费性能。这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,
 也就是⼀个类的构造⽅法在多线程环境下可以被正确的加载。
 此种⽅式也是⾮常推荐使⽤的⼀种单例模式
 */
public class Singleton_04 {
    private static class SingletonHolder {
        private static Singleton_04 instance = new Singleton_04();
    }
    private Singleton_04() {
    }
    public static Singleton_04 getInstance() {
        return SingletonHolder.instance;
    }
}
java
/**
 1. java并发库提供了很多原⼦类来⽀持并发访问的数据安全性;
 AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference。
 2. AtomicReference可以封装引⽤⼀个V实例,⽀持并发访问如上的单例⽅式就是使⽤了这样的⼀个特点。
 3. 使⽤CAS的好处就是不需要使⽤传统的加锁⽅式保证线程安全,⽽是依赖于CAS的忙等算法,依赖
 于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外
 的开销,并且可以⽀持较⼤的并发性。当然CAS也有⼀个缺点就是忙等,如果⼀直没有获取到将会处于死循环中。
 */
public class Singleton_06 {
    private static final AtomicReference<Singleton_06> INSTANCE = new
            AtomicReference<Singleton_06>();
    private static Singleton_06 instance;
    private Singleton_06() {
    }
    public static final Singleton_06 getInstance() {
        for (; ; ) {
            Singleton_06 instance = INSTANCE.get();
            if (null != instance) return instance;
            INSTANCE.compareAndSet(null, new Singleton_06());
            return INSTANCE.get();
        }
    }
}
java
public enum Singleton_08 {
    /**操作失败**/
    RC999("999","操作XXX失败"),
    /**操作成功**/
    RC200("200","success"),
    /**服务降级**/
    RC201("201","服务开启降级保护,请稍后再试!"),
    /**热点参数限流**/
    RC202("202","热点参数限流,请稍后再试!");

    /**自定义状态码**/
    private final String code;
    /**自定义描述**/
    private final String message;

    Singleton_08(String code, String message){
        this.code = code;
        this.message = message;
    }
}
// 还有就是可以像前面的单例一样有对象方法使用
public enum Singleton_09 {
    Instance;

    public void business() {
        System.out.println("hi~");
    }

    public static void main(String[] args) {
        // 调用枚举的业务方法
        Singleton_09.Instance.business();
    }
}