GuangchaoSun's Blog

Java中单例模式的有关实现

单例模式确保每个类只有一个实例,并且自行实例化向系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头

特点:

  • 单例类只能有一个实例
  • 单例类必须自己创建自己的唯一实例
  • 单例类必须给其他对象提供这一实例

懒汉式,线程不安全

1
2
3
4
5
6
7
8
9
10
11
public class SingletonClass{
private static SingletonClass instance = null;
private SingletonClass(){}
public static SingletonClass getInstance(){
if(instance == null){
instance = new SingletonClass();
}
return instance;
}
}

使用了lazy loading,但是有致命缺点。当有多个线程调用getInstance()方法的时候,就会产生多个实例,也就是说,不能用于多线程。

懒汉单例模式,线程安全

为了解决上面的问题,把getInstance方法设为同步(synchronized)

1
2
3
4
5
6
7
8
9
10
public class SingletonClass{
private static SingletonClass instance = null;
private SingletonClass(){}
public synchronized static SingletonClass getInstance(){
if(instance == null){
instance = new SingletonClass();
}
return instance;
}
}

虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。

双重检验锁

双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton{
private volatile static Singleton singleton;
private Singleton(){}
public static Singleton getSingleton(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
result singleton;
}
}

饿汉式

因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

1
2
3
4
5
6
7
8
//饿汉单例模式
public class SingletonClass{
private static final SingletonClass instance = new SingletonClass();
private SingletonClass(){}
public static SingletonClass getInstance(){
return instance;
}
}

缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

静态内部类

倾向于选择这个

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

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本

枚举Enum

1
2
3
public enum EasySingleton{
INSTANCE;
}

我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。

一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。

参考

http://cantellow.iteye.com/blog/838473
http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
http://www.cnblogs.com/lzq198754/p/5780314.html