设计模式之单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。


实现单例模式的思路

  • 一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)
  • 当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类持有的引用为空就创建该类的实例并将实例的引用赋予该类持有的引用
  • 同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例

单例模式的应用场景

  • 要求生产唯一序列号
  • Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行
  • 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件

通常单例模式在Java语言中,有两种构建方式

  • 懒汉方式:指全局的单例实例在第一次被使用时构建
  • 饿汉方式:指全局的单例实例在类装载时构建
    注意:这两种方式构建的单例类中,构造函数都是私有的,故此类不能被继承!

懒汉式,线程不安全

代码示例:

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

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

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

懒汉式,线程安全

为了解决上面的问题,最简单的方法是将整个 getInstance() 方法设为同步(synchronized)。
代码示例:

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

这种方式能够在多线程中很好的工作,但是其必须加锁synchronized才能保证单例,由于加锁会影响效率,而且99%情况下不需要同步,所以其效率很低。

双重检验锁

双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
}
}
return singleton;
}
}

这里会有两次检查instance == null,一次是在同步块外,一次是在同步块内。

饿汉式

这种方法非常简单,因为单例的实例被声明成 static 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的,但容易产生垃圾对象,浪费内存。

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

静态内部类

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程。这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。

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;
}
}

枚举

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

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

参考资料

  1. 单例模式
  2. 如何正确地写出单例模式