- 一. 单例模式概述
- 单例模式(Singleton),是一种常用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候,整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。
- 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,显然,这种方式简化了在复杂环境下的配置管理。特别地,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。事实上,这些应用都或多或少具有资源管理器的功能。例如,每台计算机可以有若干个打印机,但只能有一个 Printer Spooler (单例) ,以避免两个打印作业同时输出到打印机中。再比如,每台计算机可以有若干通信端口,系统应当集中(单例) 管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
- 单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。
- 二. 单例模式及其单线程环境下的经典实现
- 1、单例模式理论基础
- 定义: 确保一个类只有一个实例,并为整个系统提供一个全局访问点 (向整个系统提供这个实例)。
- 类型: 创建型模式
- 类图分为三部分,依次是类名、属性、方法;
- 以<<开头和以>>结尾的为注释信息;
- 修饰符+代表public,-代表private,#代表protected,什么都没有代表包可见;
- 带下划线的属性或方法代表是静态的。
- 三要素:
- 私有的构造方法;
- 指向自己实例的私有静态引用;
- 以自己实例为返回值的静态的公有方法。
- 2、单线程环境下的两种经典实现
- 立即加载 : 在类加载初始化的时候就主动创建实例 ;饿汉式单例(更好)
- 延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。懒汉式单例(在多线程可能会创建多个对象)
- 立即加载 : 在类加载初始化的时候就主动创建实例 ;饿汉式单例(更好)
- 3、单例模式的优点
- 在内存中只有一对象,节省内存空间;
- 避免频繁的创建销毁对象,可以提高性能;
- 避免对共享资源的多重占用,简化访问;
- 为整个系统提供一个全局访问点。
- 4、单例模式的使用场景
- 由于单例模式具有以上优点,并且形式上比较简单,所以是日常开发中用的比较多的一种设计模式,其核心在于为整个系统提供一个唯一的实例,其应用场景包括但不仅限于以下几种:
- 有状态的工具类对象;
- 频繁访问数据库或文件的对象;
- 1、单例模式理论基础
- 三. 多线程环境下单例模式的实现
- 1、饿汉式单例天生就是线程安全的:类加载的方式是按需加载,且只加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。换句话说,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。因此就说,饿汉式单例天生就是线程安全的。
- 2、传统的懒汉式单例为什么是非线程安全:上面发生非线程安全的一个显著原因是,会有多个线程同时进入 if (singleton2 == null) {…} 语句块的情形发生。当这种这种情形发生后,该单例类就会创建出多个实例,违背单例模式的初衷。因此,传统的懒汉式单例是非线程安全的。
- 3、实现线程安全的懒汉式单例的几种正确姿势
- 1)、同步延迟加载 — synchronized方法:
- 2)、同步延迟加载 — synchronized块
- 3)、同步延迟加载 — 使用内部类实现延迟加载
- 1)、同步延迟加载 — synchronized方法:
- 四. 单例模式与双重检查(Double-Check idiom)
- 使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率:如上述代码所示,为了在保证单例的前提下提高运行效率,我们需要对 singleton3 进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同步获取锁了)。这种做法无疑是优秀的,但是我们必须注意一点:必须使用volatile关键字修饰单例引用。操作3不是一个原子操作,他至少包含3个操作,1、开辟空间;2、对空间进行初始化 ;3、将该空间复制给变量(volatile---依旧可以重排,只不过以volatile分界,上下不能调换位置);如果此操作的顺序行发送改变,2-3对调, 等下次询问 singleton3==null,他已经将空间复制给变量,所以他不是空,就直接返回该对象引用,使用了一个没有被初始化的空间,系统崩溃;
- 使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率:如上述代码所示,为了在保证单例的前提下提高运行效率,我们需要对 singleton3 进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同步获取锁了)。这种做法无疑是优秀的,但是我们必须注意一点:必须使用volatile关键字修饰单例引用。操作3不是一个原子操作,他至少包含3个操作,1、开辟空间;2、对空间进行初始化 ;3、将该空间复制给变量(volatile---依旧可以重排,只不过以volatile分界,上下不能调换位置);如果此操作的顺序行发送改变,2-3对调, 等下次询问 singleton3==null,他已经将空间复制给变量,所以他不是空,就直接返回该对象引用,使用了一个没有被初始化的空间,系统崩溃;
- 六. 小结
- 本文首先介绍了单例模式的定义和结构,并给出了其在单线程和多线程环境下的几种经典实现。特别地,我们知道,传统的饿汉式单例无论在单线程还是多线程环境下都是线程安全的,但是传统的懒汉式单例在多线程环境下是非线程安全的。为此,我们特别介绍了五种方式来在多线程环境下创建线程安全的单例,包括:
- 使用synchronized方法实现懒汉式单例;
- 使用synchronized块实现懒汉式单例;
- 使用静态内部类实现懒汉式单例;
- 使用双重检查模式实现懒汉式单例;
- 我们可以总结出,要想实现效率高的线程安全的单例,我们必须注意以下两点:
- 尽量减少同步块的作用域;
- 尽量使用细粒度的锁。
- 本文首先介绍了单例模式的定义和结构,并给出了其在单线程和多线程环境下的几种经典实现。特别地,我们知道,传统的饿汉式单例无论在单线程还是多线程环境下都是线程安全的,但是传统的懒汉式单例在多线程环境下是非线程安全的。为此,我们特别介绍了五种方式来在多线程环境下创建线程安全的单例,包括:
单例模式解析
最新推荐文章于 2020-11-28 15:27:49 发布