简介
特点
- 单例模式保证整个应用中某个实例有且只有一个。
- 有些对象我们只需要一个,比如:
- 在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。
- 每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
- 如果创造出多个实例,就会导致许多问题,比如占用过多资源,不一致的结果等
总之,选择单例模式就是为了避免不一致状态,避免政出多头。
实现
一.饿汉模式–急着加载
单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。
- 优点
在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的 - 缺点
所以饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它
1 | public class Singleton { |
二.懒汉式–等着加载
这段代码简单明了,但是当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
1 | public class Singleton { |
并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全
整个 getInstance() 方法设为同步
最简单的方法是将整个 getInstance() 方法设为同步(synchronized)。
1 | public static synchronized Singleton getInstance() { |
虽然线程安全了,但是每次都要同步,会影响性能,毕竟大部分情况下是不需要同步的
双重检查锁定
1 | public static Singleton getInstance() { |
静态内部类
这种写法使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷。
1 | public class Singleton { |
三.枚举
1 | public enum Singleton{ |
我们可以通过 Singleton.INSTANCE 来访问实例,这比调用getInstance()方法简单多了。
- 创建枚举默认就是线程安全的,所以不需要担心double checked locking
- 而且还能防止反序列化导致重新创建新的对象
饿汉模式和懒汉模式的对比:
- 初始化时机
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。 - 特点
饿汉模式加载类时比较慢,但运行时获取对象的速度比较快,线程安全;
懒汉模式加载类时比较快,但运行时获取对象的速度比较慢,线程不安全。
选择
一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)可以使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。