单例模式

单例模式的核心代码就是将构造方法私有化,只有一个实例,自己负责创建自己的对象即自行实例化,提供一种访问其唯一对象的方式,可直接访问,不需要实例化该类的对象。

优点

  • 内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁创建和销毁时。
  • 只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可通过在应用启动时直接产生一个单例对象,永久驻留内存的方式解决。
  • 可以避免对资源的多重占用,例如写文件动作,避免了对同一个资源文件同事写操作。
  • 可以在系统设置全局访问点优化和共享资源访问。如设计一个单例类,负责所有数据表的映射处理。

缺点

  • 单例模式一般没有接口,扩展困难。单例模式要求自行实例化,且提供单一实例,接口和抽象类是不能被实例化的。
  • 对测试不利,单例模式未开发完,是不能进行测试的,没有接口也不能使用mock方式来进行测试。
  • 与单一职责原则冲突

懒汉式

实例在使用时才去创建,用的时候才去检查有没有实例。有线程安全和线程不安全两种写法,区别就是synchronized关键字。下面这种写法存在线程安全问题,在并发获取实例时,可能会存在创建多个实例的情况。

1
2
3
4
5
6
7
8
9
10
11
12
public class LazySingleton {
private static LazySingleton instance;

private LazySingleton() {}

public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

饿汉式

在类加载时实例被创建。实现简单且没有线程安全的问题,可能在还不需要此实例的时候就已经把实例创建出来了,浪费内存空间,没起到lazy loading的效果。

1
2
3
4
5
6
7
8
9
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();

private HungrySingleton() {}

private static HungrySingleton getInstance() {
return instance;
}
}

双检锁

双重校验锁,综合了懒汉式饿汉式两者的优缺点。特点在synchronized关键字内外都加了一层 if 条件判断,既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。这里还用到了volatile关键字来修饰instance,其最关键的作用是防止指令重排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static volatile Singleton instance;

private Singleton(){}

public static Singleton getInstance(){
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

静态内部类

静态内部类的方式效果类似双检锁,但实现更简单且线程安全。同时静态内部类不会在Singleton类加载时就加载,而是在调用getInstance()方法时才进行加载,达到了懒加载的效果。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

private Singleton(){}

public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

枚举

枚举的方式是比较少见的一种实现方式,却更简洁清晰。还自动支持序列化机制,绝对防止多次实例化。单元素枚举类型已经成为实现Singleton的最佳方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton {
private enum SingletonEnum {
INSTANCE;
private Singleton singleton;

private SingletonEnum() {
singleton = new Singleton();
}

public Singleton getInstance() {
return singleton;
}
}

public static Singleton getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}
}

使用场景

在系统中要求一个类有且仅有一个对象,若出现多个对象会出现副作用,可以采用单例模式。

  • 要求生成唯一序列号的环境
  • 在整个项目中需要一个共享访问点共享数据
  • 创建一个对象需要消耗的资源过多,如访问IO数据库等资源
  • 需要定义大量的静态常量和静态方法的环境

Spring中典型应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}