说明:设计模式系列文章是读刘伟所著《设计模式的艺术之道(软件开发人员内功修炼之道)》一书的阅读笔记。个人感觉这本书讲的不错,有兴趣推荐读一读。详细内容也可以看看此书作者的博客https://blog.csdn.net/LoveLion/article/details/17517213。
模式概述 模式定义实际开发中,我们会遇到这样的情况,为了节约系统资源或者数据的一致性(比如说全局的Config、携带上下文信息的Context等等),有时需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后,我们无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性,我们可以通过单例模式来实现,这就是单例模式的动机所在。
单例模式(Singleton Pattern): 确保某一个类只有一个实例,而且自己实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
单例模式有三个要点:
某个类只能有一个实例
它必须自行创建这个实例
它必须自行向整个系统提供这个实例
模式结构图单例模式是结构最简单的设计模式一,在它的核心结构中只包含一个被称为单例类的特殊类。单例模式结构图如下所示:
单例模式结构图中只包含一个单例角色:
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
饿汉式单例与懒汉式单例 饿汉式单例饿汉式单例类是实现起来最简单的单例类。由于在定义静态变量的时候实例化单例类,因此在类加载的时候就已经创建了单例对象,典型代码如下:
public class EagerSingleton { private static final EagerSingleton instance = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance() { return instance; } } 懒汉式单例懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)或者懒加载技术,即需要的时候再加载实例,为避免多线程环境下同时调用getInstance()方法从而生成多个实例,需要确保线程安全,相应实现也就有多种方式。
第一种方法可以使用关键字synchronized,代码实现如下:
public class LazySingleton { private static LazySingleton instance = null; private LazySingleton() { } public synchronized static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }在getInstance()方法前面增加了关键字synchronized进行同步,以处理多线程同时访问的安全问题。我们知道使用synchronized关键字最好是在离共享资源最近的位置加锁,这样同步带来的性能影响会减小。所以让人感觉上面的实现可以优化为如下代码:
public static LazySingleton getInstance() { if (instance == null) { synchronized (LazySingleton.class) { instance = new LazySingleton(); } } return instance; }问题貌似得以解决,事实并非如此。如果使用以上代码来实现单例,还是会存在单例对象不唯一。原因如下:
假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized修饰的代码块中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized修饰的代码块。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized中再进行一次(instance == null)判断,这种方式称为双重检查锁定(Double-Check Locking)。使用双重检查锁定实现的懒汉式单例类典型代码如下所示:
需要注意的是,如果使用双重检查锁定来实现懒汉式单例类,最好在静态成员变量instance之前增加修饰符volatile,被volatile修饰的变量可以保证多线程环境下的可见性以及禁止指令重排序。由于volatile关键字会屏蔽Java虚拟机所做的一些优化,可能对执行效率稍微有些影响,因此使用双重检查锁定来实现单例模式也不一定是最完美的实现方式。