JasonWang's Blog

单例模式在Java中的应用

字数统计: 1k阅读时长: 4 min
2017/04/29

单例模式(Singleton Pattern)用于确保系统中某个类只有一个实例存在,即该类被创建初始化一次后,之后都不会再被创建。这里就来看下单例模式在Java中常见的几种实现方式:

  • 双重检查锁
  • 类初始化
  • enum类型实现

双重检查锁

先来看下单例模式的简单实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

/*
* a very simple singleton
*
* this is not thread safe
*/

public class SimpleSingletonUnsafe {
private static SimpleSingletonUnsafe sInstance = null;

private SimpleSingletonUnsafe(){}

public static SimpleSingletonUnsafe getInstance(){
if(sInstance == null){
sInstance = new SimpleSingletonUnsafe();
}

return sInstance;

}
}

由于没有对代码进行同步保护,上述实现在多个线程并发访问时可能出现sInstance被初始化多次的情况。为避免这种情况,可以直接在声明静态变量时进行类的初始化,从而保证始终只有一次初始化动作(JVM在初始化类时会获取一个锁,确保类初始化是线程安全的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

/*
* a singleton which is thread safe
*/

public class SimpleSingletonSafe {
private static SimpleSingletonSafe INSTANCE = new SimpleSingletonSafe();

private SimpleSingletonSafe(){}

public static SimpleSingletonSafe getInstance(){
return INSTANCE;
}

}

现在有了一个线程安全的单例模式了(Eager Initialization),但是这里还存在一个问题: 不管是否有线程使用该类,它都会初始化一次。这对于那些占有很多资源的类来说,可能并不合适。能不能只是在需要使用的时候才会类进行初始化了?采用双重检查锁(double checking lock)技术即可避免这个问题:

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

/*
* a lazy singleton to prevent object creation earlier
*
* this is not thread safe
*/

public class LazySingletonUnsafe {
private static LazySingletonUnsafe sInstance = null;

private LazySingletonUnsafe(){}

public static LazySingletonUnsafe getInstance(){
if(sInstance == null){ //B: 1
synchronized(LazySingletonUnsafe.class){
if(sInstance == null){
sInstance = new LazySingletonUnsafe(); // A: 2
}
}
}

return sInstance; // B: 可能返回一个尚未初始化完成的对象
}

}


表面看起来,这个方案似乎已经大功告成了。但在多线程的情况下,可能返回一个未初始化完成的对象。比如,有两个线程A、B,线程A先获取到同步锁,进入初始化代码,此时类LazySingletonUnsafe开始初始化,此时sInstance已经指向类分配的内存空间,不为空,当线程B调用getInstance时,判断条件不成立,因此直接准备返回,而实际上返回的是sInstance的一个引用对象,而可能此时类的初始化并没有完成,B得到的类实例就与A得到的不一致了。通过在限定变量sInstancevolatile(禁止JVM进行指令重排序,确保引用sInstance在多线程情况下是可见的),即可解决该问题:

| 有关volatile在JVM中的实现原理可参考:Java并发编程之Java Memory Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24


/*
* a thread-safe singleton
*/

public class LazySingletonSafe {
private volatile static LazySingletonSafe sInstance = null;

private LazySingletonSafe(){}

public static LazySingletonSafe getInstance(){
if(sInstance == null){
synchronized(LazySingletonUnsafe.class){
if(sInstance == null){
sInstance = new LazySingletonUnsafe();
}
}
}

return sInstance;
}
}

Java类初始化的详细过程: http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2

类初始化

与此前双重检查锁中方案2类似,通过一个静态的私有类,类的对象变量作为该私有类的静态成员,该静态成员变量初始化时即创建新的对象,这样即能确保线程安全,也能保证类始终只初始化一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

/*
* a static nested holder to be singleton
*
* JVM will acquire a lock when initializing a class, so it is thread-safe
*/

public class SingletonHolder {
private static class InstanceHolder{
public static final SingletonHolder INSTANCE = new SingletonHolder();
}


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

}

enum类型实现

在《Effective Java 2nd edition》第二章里推荐一种利用enum来实现单例模式的方法,看起来似乎更为简单:

1
2
3
4
5
6
7
8
9
10

public enum EnumSingleton {
INSTANCE;

@Override
public String toString(){
return "EnumSingleton";
}
}

另外,其中还提到了单例模式下类序列化的问题:为了避免每次序列化时都创建一个新的对象,需要提供一个readResolve函数:

1
2
3
4
5

private void readResolve(){
return INSTANCE;
}

上述示例代码可参考:https://github.com/runningforlife/JavaExamples

原文作者:Jason Wang

更新日期:2021-08-11, 15:00:33

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 双重检查锁
  2. 2. 类初始化
  3. 3. enum类型实现