一、单例模式介绍

1. 解决的问题

  • 保障一个类只有一个实例。 最常见的是管制某些共享资源 (例如数据库或文件) 的拜访权限。

    运作形式是这样的: 如果创立了一个对象,同时过一会儿后决定再创立一个新对象,此时会取得之前已创立的对象, 而不是一个新对象。

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

  • 为该实例提供一个全局拜访节点。 还记得用过的那些存储重要对象的全局变量吗?它们在应用上非常不便,但同时也十分不平安,因为任何代码都有可能笼罩掉那些变量的内容,从而引发程序解体。

    和全局变量一样, 单例模式也容许在程序的任何中央拜访特定对象。同时能够爱护该实例不被其余代码笼罩。

2. 定义

单例模式是一种创立型设计模式,可能保障一个类只有一个实例,并提供一个拜访该实例的全局节点。

3. 利用场景

  • 程序中的某个类对于所有客户端只有一个可用的实例,能够应用单例模式。
  • 要更加严格地管制全局变量,能够应用单例模式。

留神:能够随时调整限度并设定生成单例实例的数量,只需批改 获取实例 办法,即 getInstance 中的代码即可实现。

4. 与其余设计模式的关系

  • 形象工厂模式生成器模式原型模式都能够用单例来实现。
  • 外观模式类通常能够转换为单例模式类,因为在大部分状况下一个外观模式就足够了。
  • 如果能将对象的所有共享状态简化为一个享元对象,那么享元模式就和单例模式相似。但这个两个设计模式有两个根本性的不同。

    1. 只会有一个单例实体,但享元类能够有多个实体,各实体的外在状态也能够不同。
    2. 单例对象能够是可变的。享元对象是不可变的。

二、单例模式优缺点

1. 长处

  • 能够保障一个类只有一个实例。
  • 取得了一个指向该实例的全局拜访节点。
  • 仅在首次申请单例对象时对其进行初始化。

2. 毛病

  • 违反了繁多职责准则。
  • 单例模式可能覆盖不良设计,比方程序各组件之间相互了解过多等。
  • 该模式在多线程环境下须要进行非凡解决,防止多个线程多次出创立单例对象。
  • 单例的客户端代码单元测试可能会比拟艰难,因为许多测试框架以基于继承的形式创立模仿对象。因为单例类的构造函数是公有的,而且绝大部分语言无奈重写静态方法,所以须要认真思考模仿单例的办法。

三、7 种单例模式实现

动态类应用

/** * 动态类单例实现 */public class StaticSingleton {    public static Map<String,String> hashMap = new ConcurrentHashMap<String, String>();}
  • 这种形式在咱们的业务开发场景中十分常见,这样的形式能够在第一次运行的时候间接初始化 Map 类。
  • 在不须要维持任何状态,不须要提早加载,仅仅用于全局拜访时,这种形式更便于应用。
  • 但在须要被继承、须要维持一些特定状态时,单例模式更适宜。

单例模式的实现形式比拟多,次要是实现上是否反对懒汉模式、是否线程平安。

接下来通过不同的单例模式实现形式来解析单例模式。

1. 懒汉单例模式(线程不平安)

/** * 懒汉单例模式(线程不平安) */public class SlobThreadUnsafeSingleton {    private static SlobThreadUnsafeSingleton instance;    /**     * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法     */    private SlobThreadUnsafeSingleton() {    }    public static SlobThreadUnsafeSingleton getInstance() {        if (ObjectUtils.isEmpty(instance)) {            instance = new SlobThreadUnsafeSingleton();        }        return instance;    }}

这种懒汉单例模式满足了懒加载,但当多个访问者同时获取对象实例时(多过程),会导致多个实例并存,没有达到单例模式的需要。

2. 懒汉单例模式(线程平安)

/** * 懒汉单例模式(线程平安) */public class SlobThreadSafeSingleton {    private static SlobThreadSafeSingleton instance;    /**     * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法     */    private SlobThreadSafeSingleton() {    }    public static synchronized SlobThreadSafeSingleton getInstance() {        if (ObjectUtils.isEmpty(instance)) {            instance = new SlobThreadSafeSingleton();        }        return instance;    }}

这种懒汉单例模式是线程平安的,但因为锁在办法上,所有的拜访都须要锁占用,会导致资源的节约。非非凡状况,不倡议应用此种形式来实现单例模式。

3. 饿汉单例模式(线程平安)

/** * 饿汉单例模式(线程平安) */public class EagerSingleton {    private static EagerSingleton instance = new EagerSingleton();    /**     * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法     */    private EagerSingleton() {    }    private static EagerSingleton getInstance() {        return instance;    }}

饿汉单例模式并不是懒加载,简略来说就是无论是否用到该类都会在程序启动之初创立。这种形式会导致的问题相似一关上某个软件,手机间接卡死(开启了过多无用性能,导致内存不足)。

4. 双重校验锁单例模式(线程平安)

/** * 双重校验锁单例模式(线程平安) */public class DoubleCheckingLockingSingleton {    private static volatile DoubleCheckingLockingSingleton instance;    /**     * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法     */    private DoubleCheckingLockingSingleton() {    }    public static DoubleCheckingLockingSingleton getInstance() {        if (ObjectUtils.isNotEmpty(instance)) {            return instance;        }        synchronized (DoubleCheckingLockingSingleton.class) {            if (ObjectUtils.isEmpty(instance)) {                instance = new DoubleCheckingLockingSingleton();            }        }        return instance;    }}

双重校验锁形式实现的单例模式是办法级锁的优化,缩小了局部获取实例的耗时,同时也满足了懒加载。

5. 动态外部类单例模式(线程平安)

/** * 动态外部类单例模式(线程平安) */public class StaticInnerSingleton {    private static class SingletonHolder {        private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();    }    /**     * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法     */    private StaticInnerSingleton(){    }    public static final StaticInnerSingleton getInstance() {        return SingletonHolder.INSTANCE;    }}

动态外部类实现的单例模式,既保证了线程平安,也保障了懒加载,同时不会因为加锁的形式导致性能开销过大。

这次要是因为 JVM 虚拟机能够保障多过程并发拜访的正确性,即一个类的构造方法在多线程环境下,能够被正确加载。

因而,动态类外部类的实现形式十分举荐应用。

6. CAS「AtomicReference」单例模式(线程平安)

/** * CAS「AtomicReference」单例模式(线程平安) */public class CompareAndSwapSingleton {    private static final AtomicReference<CompareAndSwapSingleton> INSTANCE = new AtomicReference<CompareAndSwapSingleton>();    private static CompareAndSwapSingleton instance;    /**     * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法     */    private CompareAndSwapSingleton() {    }    public static final CompareAndSwapSingleton getInstance() {        for (; ; ) {            CompareAndSwapSingleton instance = INSTANCE.get();            if (ObjectUtils.isNotEmpty(instance)) {                return instance;            }            INSTANCE.compareAndSet(null, new CompareAndSwapSingleton());            return INSTANCE.get();        }    }}

Java 并发库提供了很多原子类来反对并发拜访的数据安全性:AtomicIntegerAtomicBoolean AtomicLongAtomicReference

AtomicReference 能够封装援用一个实例,反对并发拜访,该单例形式就是应用该特点实现。

采纳 CAS 形式的长处就是不须要传统的加锁形式来保障线程平安,而是依赖 CAS 的忙等算法,依赖底层硬件的实现,来保障线程平安。绝对于其余锁的实现没有线程的切换和阻塞,也就没有了额定开销,因而能够反对较大的并发。

当然,CAS 形式也存在毛病,忙等即如果始终没有获取到将会处于死循环中。

7. 枚举单例模式(线程平安)

/** * 枚举单例(线程平安) */public enum EnumSingleton {    INSTANCE;    public void whateverMethod() {        System.out.println("enum singleton method");    }}
约书亚·布洛克(英语:Joshua J. Bloch,1961年8⽉28⽇-),美国驰名程序员,《Effective Java》作者。他为Java平台设计并实作了许多的性能,曾负责Google的⾸席Java架构师(Chief Java Architect)。

《Effective Java》作者约书亚·布洛克举荐应用枚举的形式解决单例模式,这种形式应该是平时起码用到的。

这种形式解决的单例模式的次要问题:线程平安、自在串行化、繁多实例。

调用形式

@Testpublic void testEnumSingleton(){    EnumSingleton.INSTANCE.whateverMethod();}

这种写法在性能上与共有域⽅法相近,然而它更简洁,⽆偿地提供了串⾏化机制,相对防⽌对此实例化,即便是在⾯对简单的串⾏化或者反射攻打的时候。尽管这种形式还没有⼴泛采⽤,然而单元素的枚举类型曾经成为实现单例 的最佳⽅法。

但这种⽅式在存在继承场景下是不可⽤的。

四、单例模式构造

  1. 单例(Singleton)类申明了一个名为 getInstance 获取实例的静态方法来返回其所属类的一个雷同实例。
  2. 单例的结构模式必须对客户端(Client)代码暗藏。调用 获取实例 办法必须是获取单例对象的惟一形式。

设计模式并不难学,其自身就是多年教训提炼出的开发指导思想,关键在于多加练习,带着应用设计模式的思维去优化代码,就能构建出更正当的代码。

源码地址:https://github.com/yiyufxst/d...

参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei...
深刻设计模式:https://refactoringguru.cn/de...