Singleton Design Pattern
设计模式 - 单例模式(Singleton Design Pattern)
Conception
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
单例模式解决的问题:
- 处理资源访问冲突(对象级别的锁,且对象唯一)
例如: Logger等容易造成资源冲突的对象。
- 表示全局唯一类
例如:配置文件;唯一递增ID号码生成器等
实现单例模式的要点
- 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
- 考虑对象创建时的线程安全问题;
- 考虑是否支持延迟加载;
- 考虑 getInstance() 性能是否高(是否加锁)。
单例存在的问题
- 单例对OOP特性的支持不友好(并非面向接口编程)
- 单例会隐藏类之间的依赖关系
- 单例对代码的扩展性不友好
- 单例对代码的可测试性不友好
- 单例不支持有参数的构造函数
替代方案: 工厂模式、IOC容器
Implementation
单例模式有5中经典的实现方式:
- 饿汉式
public class IdGenerator { private final AtomicLong id = new AtomicLong(0); private static final IdGenerator instance = new IdGenerator(); private IdGenerator() {} public static IdGenerator getInstance() { return instance; } public long getId() { return id.incrementAndGet(); } }
- 在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。
- 不支持延迟加载实例
- 懒汉式
public class IdGenerator { private final AtomicLong id = new AtomicLong(0); private static IdGenerator instance; private IdGenerator() {} public static synchronized IdGenerator getInstance() { if (instance == null) { instance = new IdGenerator(); } return instance; } public long getId() { return id.incrementAndGet(); } }
- 支持延迟加载
- 会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈
- 双重检测
public class IdGenerator { private final AtomicLong id = new AtomicLong(0); private static IdGenerator instance; private IdGenerator() {} public static IdGenerator getInstance() { if (instance == null) { synchronized(IdGenerator.class) { if (instance == null) instance = new IdGenerator(); } } return instance; } public long getId() { return id.incrementAndGet(); } }
既支持延迟加载、又支持高并发
Question: 为什么要二次判断
instance == null
- 当instance未创建时,instance == null 为true
- 多个线程同时进入第一层if
- 此时开始竞争资源
- 线程1 抢到资源, 进行二次判断, instance == null 为true
- 创建instance
- 释放锁
- 线程2 抢到资源,进行二次判断, instance == null 为false
- 这样不会创建新的instance
- 静态内部类
public class IdGenerator { private final AtomicLong id = new AtomicLong(0); private static class SingletonHolder { private static final IdGenerator instance = new IdGenerator(); } private IdGenerator() {} public static IdGenerator getInstance() { return SingletonHolder.instance; } public long getId() { return id.incrementAndGet(); } }
支持延迟加载,也支持高并发,实现起来也比双重检测简单。 instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。
- 枚举
public enum IdGenerator { INSTANCE; private final AtomicLong id = new AtomicLong(0); public long getId() { return id.incrementAndGet(); } }
最简单的实现方式,基于枚举类型的单例实现。
如何实现集群下的单例模式?
我们需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。
进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。
为了保证任何时刻在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。
在进程使用完这个对象之后,需要显式地将对象从内存中删除,并且释放对对象的加锁。
Comments