单例模式

2019-05-30 998 ℃

定义

单例模式确保一个类只有一个实例,并提供一个全局访问点。

单例模式的要点有三个:

  • 一是某个类只能有一个实例;
  • 二是它必须自行创建这个实例;
  • 三是它必须自行向整个系统提供这个实例。

结构

  • 单例(Singleton): 构造方法

策略模式UML类图

优点

  • 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
  • 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。

缺点

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出。

适用场景

  • 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。
  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
  • 在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就需要对单例模式进行改进,使之成为多例模式

代码实现

懒汉式(线程不安全)

public class UnsafeLazySingleton { private static UnsafeLazySingleton INSTANCE; private UnsafeLazySingleton() {} public static UnsafeLazySingleton getInstance() { if (INSTANCE == null) { INSTANCE = new UnsafeLazySingleton(); } return INSTANCE; } }

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述: 这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

优点: 达到了Lazy Loading的效果

缺点: 不支持多线程

推荐指数: ★☆

饿汉式(线程安全)

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

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述: 这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

优点: 达到了Lazy Loading的效果,支持多线程

缺点: 效率低

推荐指数: ★★

饿汉式

public class EagerSingleton { private final static EagerSingleton INSTANCE = new EagerSingleton(); private EagerSingleton(){} public static EagerSingleton getInstance(){ return INSTANCE; } }

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述: 这种方式比较常用,但容易产生垃圾对象。

优点: 没有加锁,执行效率提高,避免了线程同步问题。

缺点: 在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

推荐指数: ★★★★☆

双检锁/双重校验锁(DCL,即 double-checked locking)

public class DclSingleton { private static volatile DclSingleton INSTANCE; private DclSingleton(){} public static DclSingleton getInstance(){ if (INSTANCE == null) { synchronized (DclSingleton.class) { if (INSTANCE == null) { INSTANCE = new DclSingleton(); } } } return INSTANCE; } }

是否 Lazy 初始化:

是否多线程安全:

实现难度: 较复杂

描述: 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

优点: 线程安全;延迟加载;效率较高

缺点: 实现较复杂,需JDK5以上版本

推荐指数: ★★★★

登记式/静态内部类

public class StaticInnerSingleton { private StaticInnerSingleton(){} private static class SingletonInstance { private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton(); } public static StaticInnerSingleton getInstance() { return SingletonInstance.INSTANCE; } }

是否 Lazy 初始化:

是否多线程安全:

实现难度: 一般

描述: 这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

优点: 线程安全,延迟加载,效率高。

缺点: 只适用于静态域

推荐指数: ★★★☆

枚举

public enum EnumSingleton { INSTANCE; public void whateverMethod() { } }

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述: 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

优点: 绝对安全,实现简单,效率高。

缺点: 需JDK5以上版本

推荐指数: ★★★★★

建议

不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

版权声明:周华个人博客原创文章,转载请注明出处。

文章链接:http://www.iszhouhua.com/singleton-pattern.html

发表时间:2019-05-30 22:44

最后更新时间:2019-05-30 22:51