单例模式

本文主要介绍了几种常见的单例模式实现方式,包括饿汉式、懒汉式(线程不安全)、懒汉式(线程安全)、双重检查加锁(DCL)、静态内部类单例模式和枚举单例。其中,饿汉式和懒汉式(线程不安全)都存在线程安全问题,懒汉式(线程安全)虽然解决了线程安全问题,但效率较低。双重检查加锁(DCL)虽然解决了线程安全和效率问题,但在某些情况下会出现失效的问题。静态内部类单例模式和枚举单例都是线程安全的,且实现简单,推荐使用。此外,还介绍了如何避免单例对象被反序列化时重新生成对象的方法。单例模式在实际开发中应用广泛,对于保证系统中某些对象的唯一性具有重要意义。

单例模式

在软件工程中,单例模式 是一种软件设计模式,它将类的实例化限制为一个“单个”实例。当恰好需要一个对象来协调整个系统中的操作时,这是非常有用的。这个术语来自于单例的数学概念。单例模式中的“单例”通常用来代表那些本质上具有唯一性的系统组件(或者叫做资源),比如文件系统、资源管理器等等。

原文请看 刘望舒 博客 设计模式(二)单例模式的七种写法

饿汉式

1
2
3
4
5
6
7
public class Singleton{
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}

这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快,这种基于类加载机制的方式避免了多线程的同步问题。

懒汉式(线程不安全)

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

懒汉模式声明了一个静态对象,在用户第一次调用时进行初始化,虽然节约了资源,但第一次加载时需要实例化,反应稍慢,而且多线程下不能保证唯一单例。

懒汉式(线程安全)

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

线程安全,但是每次调用 getInstance 方法时都需要进行同步,造成不必要的同步开销,而且大部分时候是不需要同步的,所以不建议用这种模式。

双重检查加锁(DCL)

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

这种写法在 getInstance 方法中对 singleton 进行了两次判空,第一字是为了不必要的同步,第二次是在 singleton 等于 null 的情况下才创建实例。

DCL 优点是资源利用率高,第一次执行 getInstance 时单例对象才被实例化,效率高。缺点是第一次加载时反应稍慢,在高并发环境下也有一定的缺陷,虽然发生的概率很小。

DCL 虽然在一定程度解决了资源的消耗和多余的同步,线程安全等问题,但在某些情况也会出现失效的问题,也就是 DCL 失效,在《java并发编程实践》中建议用 静态内部类单例模式 来替代 DCL。

静态内部类单例模式

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

第一次加载 Singleton 类时并不会初始化 singleton,只要第一次调用 getInstance 方法时,虚拟机才加载 SingletonHolder 并初始化 singleton,这样不仅能确保线程安全也能保证 Singleton 类的唯一性,所以推荐使用静态内部类单例模式。

枚举单例

1
2
3
4
public enum Singleton{
INSTANCE;
public void anyMethod(){}
}

默认枚举实例的创建是线程安全的,并且在任何情况下都是单例.

而上述讲的几种单例模式实现中,有一种情况下他们会重新创建对象,那就是反序列化,将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。

反序列化操作提供了 readResolve 方法,这个方法可以让开发人员控制对象的反序列化。在上述的几个方法示例中如果要杜绝单例对象被反序列化是重新生成对象,就必须加入如下方法:

1
2
3
private Object readResolve() throws ObjectStreamException{
return singleton;
}

枚举单例的优点就是简单,但是大部分应用开发很少用枚举。

References

https://en.wikipedia.org/wiki/Singleton_pattern

https://blog.csdn.net/itachi85/article/details/50510124