人的知识就好比一个圆圈,圆圈里面是已知的,圆圈外面是未知的。你知道得越多,圆圈也就越大,你不知道的也就越多。

0%

设计模式--单例模式

定义

单例模式确保一个类只有一个实例,并提供一个全部访问点。

要素

  • 私有构造方法
  • 私有静态引用指向自己实例
  • 以自己实例为返回值的公有静态方法

类图

Singleton UML

实现方式

饿汉式

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

private Singleton() {
}

public static Singleton getSingleton() {
return instance;
}
}

饿汉式 & 枚举

1
2
3
public enum Singleton {
INSTANCE;
}

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Singleton {
/**
* volatile:保证线程间变量可见性
*/
private volatile static Singleton instance;

private Singleton() {
// 可能存在成员初始化操作,这时需要使用volatile关键字
}

public static Singleton getInstance() {
if (instance == null) {
// 只在第一次初始化时才同步
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

懒汉式 & 内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {

private Singleton() {
}

public static Singleton getInstance() {
return SingletonInner.instance;
}

private static class SingletonInner {
private static Singleton instance = new Singleton();
}
}

饿汉式 & 枚举

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
27
public class Singleton {
private static Singleton instance;

private Singleton() {
}

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

private enum SingletonEnum {
/**
* 唯一值
*/
INSTANCE;

private Singleton singleton;

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

public Singleton getInstance() {
return singleton;
}
}
}

小结

  • 实例较大,且不需要立即加载时,使用懒汉模式
  • 饿汉模式,优先使用枚举
  • 懒汉模式,优先使用静态类
  • 使用枚举,能避免使用反射或序列化/反序列化来创建多个实例
  • 使用了多个类加载器时,有可能会创建多个实例
  • 静态类相较于单例模式:仅在类自给自足,且不依赖于复杂的初始化,才可以考虑使用,不然可能会产生一些微妙的和初始化顺序有关的bug
  • 全局变量相较于单例模式:缺点有:1.不能延迟初始化;2.不能确保只有一个实例;3.污染命名空间

应用场景

  • 数据库连接池
  • 线程池
  • web应用配置对象的读取
  • 注册表
  • 网站计数器

模式应用

Unsafe

java不能直接访问操作系统底层,而是通过本地方法来访问,Unsafe类提供了硬件级别的原子操作,它就是个单例:

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
27
28
public final class Unsafe {
private static final Unsafe theUnsafe;

...

private Unsafe() {
}

...

@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}

...

static {
...
theUnsafe = new Unsafe();
...
}
}

Spring IoC

Spring容器注入的Bean实例默认都是单例的。
Bean的注入(包括lazy-init方式)都是发生在AbstractBeanFactorygetBean里,getBean调用doGetBeandoGetBean调用DefaultSingletonBeanRegistry里的getSingleton
lazy-init方式(lazy-init=”true”),在用户向容器第一次索要bean时进行调用;非lazy-init方式(lazy-init=”false”),在容器初始化时候进行调用。

同步线程安全的单例核心代码:

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
27
28
29
30
31
32
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从缓存(ConcurrentHashMap)中获取bean实例
Object singletonObject = this.singletonObjects.get(beanName);
// 如果bean实例为null,且正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // double-check:1
synchronized (this.singletonObjects) {
// 从缓存(ConcurrentHashMap)中获取early bean实例
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果bean实例为null,且允许创建early reference
if (singletonObject == null && allowEarlyReference) { // double-check:2
// 这里并非bean实例,而是创建bean实例的工厂对象
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 返回真正的bean
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

Spring中的Bean的单例虽然是一种单例效果,但实现方式是通过容器缓存实现,严格来说是一种享元模式

实际应用

需求:根据单据类型,找到对应的service,执行审批相关扩展操作,如审批前、审批后、弃审前、弃审后。
实现:提供注册单例,将单据类型及对应的service注册到单例中。
代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum ApproveServiceRegistry {
INSTANCE;
private Map<String, ApproveServiceInfo<? extends BaseBillEntity>> registry = new ConcurrentHashMap<>();

public <T extends BaseBillEntity> void register(String billType, ApproveService<T> service) {
registry.put(billType, new ApproveServiceInfo<>(service, service.getEntityClass(), service.getBillType()));
}

public void remove(String billType) {
registry.remove(billType);
}

public boolean contains(String billType) {
return registry.containsKey(billType);
}

public ApproveServiceInfo<? extends BaseBillEntity> getService(String billType) {
return registry.get(billType);
}
}
小礼物走一走,来 Github 关注我