设计模式主要分为三大类,创建型,结构型和行为型。
创建型设计模式主要解决的是对象创建的问题。
单例模式(Singleton Design Pattern)是最常见的创建型设计模式。
单例模式是什么?
一个类只能创建一个对象(也可以称为实例),这个类就是单例类,这种设计模式就是单例设计模式。
为什么需要单例模式?
需要使用单例模式或者使用单例模式更好的场景有很多,以下简单举两个场景的例子。
解决资源冲突问题
比如我们有一个诉求,我们需要系统把所有的日志都打印到一个文件中。这时候我们需要实现一个日志类。多线程的环境下,我们就会创建多个日志类,多个日志类实例并发往文件中打印文件的时候,有可能会出现彼此覆盖的情况。这时候把日志类设计成一个单例,就可以解决这个问题。
某些类从业务属性来说,就应该是单例
比如读取系统的配置文件,读到内存中,构建一个属性类。大多数系统只能有一套配置文件,因为配置类也应该只有一个实例。而且创建多个对象还会造成资源的浪费。
怎样实现单例模式
实现单例模式要考虑以下几个问题:
- 单例类的构造函数必须是private,这样才能避免外部通过new创建实例
- 考虑创建对象的线程安全问题
- 是否支持延迟加载
- 考虑getInstance()性能是否高(是否加锁)。
目前主要有5种方案来实现单例模式
饿汉模式
饿汉模式不支持延迟加载。在类加载的时候,instance实例就已经创建好了。
个人比较推崇饿汉模式。实现简单。而且能提早暴露问题。
public class IdGeneratorEager {
private AtomicLong id = new AtomicLong(0);
private static final IdGeneratorEager instance = new IdGeneratorEager();
private IdGeneratorEager() {}
public static IdGeneratorEager getInstance() {
return instance;
}
public Long getId() {
return id.getAndAdd(1);
}
}
懒汉式
懒汉式支持延迟加载,但是需要给getInstance加一把大锁。在多并发的环境下,非常影响并发度,影响系统性能。
public class IdGeneratorLazy {
private final AtomicLong id = new AtomicLong(0);
private static IdGeneratorLazy instance;
private IdGeneratorLazy() {}
public static synchronized IdGeneratorLazy getInstance() {
if (instance == null) {
instance = new IdGeneratorLazy();
}
return instance;
}
public Long getId() {
return id.getAndAdd(1);
}
}
双重检测
基于懒汉式做的改进。
public class IdGeneratorDoubleCheck {
private AtomicLong id = new AtomicLong(0);
private static volatile IdGeneratorDoubleCheck instance;
private IdGeneratorDoubleCheck(){}
public static IdGeneratorDoubleCheck getInstance() {
if (instance == null) {
synchronized (IdGeneratorDoubleCheck.class) {
if (instance == null) {
instance = new IdGeneratorDoubleCheck();
}
}
}
return instance;
}
public Long getId() {
return id.getAndAdd(1);
}
}
静态内部类
比双重检测的实现简单一点。而且支持延迟加载
public class IdGeneratorStaticNested {
private AtomicLong atomicLong = new AtomicLong(0);
private IdGeneratorStaticNested() {}
private static class SingletonHolder {
static IdGeneratorStaticNested instance = new IdGeneratorStaticNested();
}
public static IdGeneratorStaticNested getInstance() {
return SingletonHolder.instance;
}
public Long getId() {
return atomicLong.getAndAdd(1);
}
}
单例枚举实现
只能有一个枚举值。最简单的方式。
public enum IdGeneratorEnumImpl {
INSTANCE;
private AtomicLong atomicInteger = new AtomicLong(0);
public Long getId() {
return atomicInteger.getAndAdd(1);
}
}
以上代码均进行过验证
public class SingletonPattern {
public static void main(String[] args) {
//饿汉模式
IdGeneratorEager idGen1 = IdGeneratorEager.getInstance();
System.out.println(idGen1.getId());
//懒汉模式
IdGeneratorLazy idGen2 = IdGeneratorLazy.getInstance();
System.out.println(idGen2.getId());
//双重检测
IdGeneratorDoubleCheck idGen3 = IdGeneratorDoubleCheck.getInstance();
System.out.println(idGen3.getId());
//静态内部类实现
IdGeneratorStaticNested idGen4 = IdGeneratorStaticNested.getInstance();
System.out.println(idGen4.getId());
//枚举实现
System.out.println(IdGeneratorEnumImpl.INSTANCE.getId());
}
}
单例模式的问题
- 单例对OOP特性的支持不友好
- 单例会隐藏类之间的依赖关系
- 单例对代码的扩展性不友好
- 单例对代码的可测试性不友好
- 单例不支持有参数的构造函数
单例模式扩展
怎样理解单例模式的唯一性
单例模式的唯一性是进程级别的唯一。
怎样实现线程级别的唯一性
ThreadLocal实现方案,创建ThreadLocal实例对象,并且重写initialValue方法
public class IdGeneratorThreadLocal {
private static final ThreadLocal<IdGeneratorThreadLocal> threadLocal = ThreadLocal.withInitial(IdGeneratorThreadLocal::new);
private IdGeneratorThreadLocal() {}
public static IdGeneratorThreadLocal getInstance() {
return threadLocal.get();
}
}
HashMap实现方案
public class IdGeneratorThreadLevel {
private AtomicLong atomicLong = new AtomicLong(0);
private static final Map<Long, IdGeneratorThreadLevel> map = new HashMap<>();
private IdGeneratorThreadLevel() {}
public static IdGeneratorThreadLevel getInstance() {
Long threadId = Thread.currentThread().getId();
map.putIfAbsent(threadId, new IdGeneratorThreadLevel());
return map.get(threadId);
}
public Long getId() {
return atomicLong.getAndAdd(1);
}
}
多例模式怎样实现
public class IdGeneratorMultiInstance {
private AtomicLong id = new AtomicLong(0);
public static final ConcurrentHashMap<Integer, IdGeneratorMultiInstance> map = new ConcurrentHashMap<>();
private static final int count = 3;
private IdGeneratorMultiInstance() {}
static {
for (int i = 0; i < count; i++) {
map.put(i, new IdGeneratorMultiInstance());
}
}
public IdGeneratorMultiInstance getInstance(int id) {
return map.get(id);
}
public IdGeneratorMultiInstance randomGetInstance() {
Random random = new Random();
int id = random.nextInt(count) + 1;
return map.get(id);
}
public Long getId(){
return id.getAndAdd(1);
}
}
集群级别的单例
通过分布式锁来实现。每次获取id从共享存储中读。