一、单例模式介绍

单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。确切地讲就是指在某个系统中只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。主要解决一个全局使用的类被频繁的创建和销毁的问题。

以下是实现单例模式类的几个要点:

  • 单例类只能有一个实例,不允许其他程序使用 new 创建该类的对象,使用 private 的构造函数来实现这一点。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例,对外提供一个 public 方法,让其他程序可以获得该对象

单例模式的优点:

  • 在内存中只有一个实例,减少内存的开销。
  • 避免对资源的多重占用

单例模式的缺点:

  • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑而不关心外部如何来实例化。

单例模式的使用场景:

  • 要求产生唯一序列号
  • web 中的计数器,不用每次刷新都在数据库中加一次,用单例先缓存起来
  • 创建一个对象需要消耗的资过多,比如 I/O 与数据库的连接等

二、实现

单例模式的实现方式分为:

  • 懒汉式
  • 饿汉式

懒汉式和饿汉式的区别就是懒汉式延时创建对象,在第一次调用时才初始化对象,而饿汉式在类加载时就初始化了对象。

懒汉式的好处是尽可能地节省了存储空间。但是在多线程中,懒汉式可能会造成类的对象在内存中不唯一的问题,虽然可以通过加锁解决此问题,但是效率却大大降低了。

饿汉式在实际开发中使用的比较多,一般情况下不建议使用懒汉式,只有在明确要实现延时加载时,才会使用懒汉式,如果涉及到反序列化时,可以使用枚举式。

1、饿汉式

饿汉式实现单例模式,没有延迟初始化,并且线程安全,实现也更加简单。不用加锁,效率会更高,在类加载时就初始化,它与类同在,早于内存堆中的对象实例化,该类在内存中永生,内存垃圾收集器不会对其进行回收。

饿汉式在类加载时就已经创建好了一个静态对象供系统使用,以后不再改变,基于 ClassLoader 机制避免了多线程同步问题。

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 {

private static final Singleton singleton = new Singleton();

private Singleton() {
}

public static Singleton getInstance() {
return singleton;
}

public void show() {
System.out.println("hello world!");
}
}

public class Test {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
singleton.show();
}
}

2、懒汉式

延迟初始化,无请求就不实例化,在第一次调用时才初始化对象,节省内存空间。

懒汉模式的缺点是第一次请求的时候速度较之前的饿汉初始化模式慢,因为要消耗 CPU 资源去临时创建单例对象。

2.1 线程不安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton {

private static Singleton singleton;

private Singleton() {
}

public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}

public void show() {
System.out.println("hello world!");
}
}

这种方式是最基本的实现方式,但最大的问题是不支持多线程,因为没有加锁 synchronized

如果是并发请求,并且之前没有进行过调用也就是第一次初始化时,判空逻辑会同时成立,这样就会多次实例化对象,并且进行多次赋值操作,这违背了单例的理念。

2.2 线程安全

可以很好的延时加载,并且能够在多线程的情况下很好的工作,但是效率很低,大部分情况下都不需要同步。

2.2.1 同步函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton2 {

private static Singleton2 singleton;

private Singleton2() {
}

public static synchronized Singleton2 getInstance() {
if (singleton == null) {
singleton = new Singleton2();
}
return singleton;
}

public void show() {
System.out.println("hello world!");
}
}

可以直接使用同步函数,但是同步函数的方法效率比较低,每次调用该方法都需要判断锁。线程还未进入 getInstance 方法内部便加锁排队,会造成线程阻塞,造成资源和时间的浪费。只是为了实例化一个对象,没必要使用同步方法让所有请求排队等候。

2.2.2 同步代码块

也可以使用同步代码块,但是此方式仍然是对方法内的所有代码都加锁,每一次调用时都需要判断锁等待锁,没有改变效率问题。

1
2
3
4
5
6
7
8
public static Singleton2 getInstance() {
synchronized (Singleton2.class) {
if (singleton == null) {
singleton = new Singleton2();
}
return singleton;
}
}
2.2.3 双检锁(双重校验锁)

采用双检锁机制,线程安全且在多线程情况下能保持高性能。

1
2
3
4
5
6
7
8
9
10
11
public volatile static Singleton2 getInstance() {
if (singleton == null) {
synchronized (Singleton2.class) {
if (singleton == null) {
singleton = new Singleton2();
}
return singleton;
}
}
return singleton;
}

方法上不加 synchronized 关键字,使并发请求都可以同时进入方法。

如果单例对象已经初始化完成,则并发请求可以同时获取单例对象。

如果单例对象尚未初始化完成,则进入第一个判空逻辑,此时的同步块会防止多个线程同时进入以对单例对象进行初始化。获取到锁的线程进入第二个判空代码实例化对象,实例化完成之后释放锁。之前等待锁的线程获取到锁之后判断实例对象不为空,则跳过实例化代码,直接获取到单例对象。

上面的两个嵌套的判空逻辑,即为双检锁懒汉模式,在外层放宽入口,保证线程并发的高效性;内层加锁同步,保证实例化的单次运行。

3、使用静态内部类的登记式

可以达到延时加载的效果,并且是线程安全的。利用了类加载机制来保证初始化时只有一个线程,与饿汉式不同的是,虽然 Registered 被加载了,但是 instance 不一定被初始化,因为 RegisteredInner 没有被主动使用,只有当调用 getInstance() 方法时,才会装载 RegisteredInner 类。

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

private static class RegisteredInner {
private static final Registered INSTANCE = new Registered();
}

private Registered() {
}

public static final Registered getInstance() {
return RegisteredInner.INSTANCE;
}
}

三、单例模式示例

1、Hutool 中的单例

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import cn.hutool.core.lang.Assert;  
import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;

import java.util.concurrent.ConcurrentHashMap;

public final class Singleton {

private static final ConcurrentHashMap<String, Object> POOL = new ConcurrentHashMap<>();

private Singleton(){
}

public static <T> T get(String key, Class<T> clazz, Object... params) {
Assert.notNull(clazz, "Class must be not null !");
final String completeKey = buildCompleteKey(key, clazz.getName(), params);
return get(completeKey, () -> ReflectUtil.newInstance(clazz, params));
}

public static <T> T get(String key, String className, Object... params) {
Assert.notBlank(className, "Class name must be not blank !");
final Class<T> clazz = ClassUtil.loadClass(className);
return get(key, clazz, params);
}

public static <T> T get(Class<T> clazz, Object... params) {
return get("", clazz, params);
}

public static <T> T get(String className, Object... params) {
return get("", className, params);
}

@SuppressWarnings("unchecked")
public static <T> T get(String key, Func0<T> supplier) {
return (T) POOL.computeIfAbsent(key, (k)-> supplier.callWithRuntimeException());
}

public static void destroy() {
POOL.clear();
}

private static String buildCompleteKey(String key, String className, Object... params) {
if (ArrayUtil.isEmpty(params)) {
return StrUtil.format("{}#{}", key, className);
}
return StrUtil.format("{}#{}#{}", key, className, ArrayUtil.join(params, "_"));
}

}

上面的代码为对 cn.hutool.core.lang.Singleton 的参考。提供单例对象的统一管理,当调用 get 方法时,如果对象池中存在此对象,返回此对象,否则创建新对象返回

2、Mybatis-plus 中默认的 ID 生成器

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
33
34
35
36
37
38
39
40
41
public class DefaultIdentifierGenerator implements IdentifierGenerator {

private final Sequence sequence;

/**
* @see #getInstance()
* @deprecated 3.5.3.2 共享默认单例
*/
@Deprecated
public DefaultIdentifierGenerator() {
this.sequence = new Sequence(null);
}

public DefaultIdentifierGenerator(InetAddress inetAddress) {
this.sequence = new Sequence(inetAddress);
}

public DefaultIdentifierGenerator(long workerId, long dataCenterId) {
this.sequence = new Sequence(workerId, dataCenterId);
}

public DefaultIdentifierGenerator(Sequence sequence) {
this.sequence = sequence;
}

@Override
public Long nextId(Object entity) {
return sequence.nextId();
}

public static DefaultIdentifierGenerator getInstance() {
return DefaultInstance.INSTANCE;
}

private static class DefaultInstance {

public static final DefaultIdentifierGenerator INSTANCE = new DefaultIdentifierGenerator();

}

}

相关链接

OB tags

#设计模式