文章目录
  1. 1. 单例模式介绍
  2. 2. 单例模式UML图:
  3. 3. 实现方式:
    1. 3.1. 懒汉式,非线程安全
    2. 3.2. 懒汉式,线程安全
    3. 3.3. 饿汉式
    4. 3.4. 双重检验锁(DCL)
    5. 3.5. 静态内部类
    6. 3.6. 枚举
  4. 4. 总结

单例模式介绍

单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。

关键点:

单例模式的要点有三个:

  1. 一是某个类只能有一个实例.
  2. 二是它必须自行创建这个实例.
  3. 三是它必须自行向整个系统提供这个实例。

从具体实现角度来说,就是以下三点:

  1. 一是单例模式的类只提供私有的构造方法.
  2. 二是类定义中含有一个该类的静态私有对象.
  3. 三是该类提供了一个静态的公有的方法用于创建或获取它本身的静态私有对象。

单例模式UML图:

实现方式:

懒汉式,非线程安全

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

private Singleton() { }

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

缺点:在多线程环境下可能产生多个实例,违背单例原则。

懒汉式,线程安全

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

private Singleton() {}

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

虽然做到了线程安全,并且解决了多实例的问题,但是在方法级别上synchronized并不高效,理论上只需要在第一次调用getInstance() 才需要同步操作。

饿汉式

1
2
3
4
5
6
7
8
9
10
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();

private Singleton(){}

public static Singleton getInstance(){
return instance;
}
}

第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。在延迟加载的场景并不适用。

双重检验锁(DCL)

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

public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
缺点:实现比较复杂,并且由于Java5 以前的JMM(Java 内存模型)存在缺陷,该实现方式会有问题, Java5 和更高的版本可以使用。

静态内部类

1
2
3
4
5
6
7
8
9
10
11
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版本。

枚举

1
2
3
public enum Singleton{
INSTANCE;
}

该方法是《Effective Java》上推荐的写法。
优点:实现很简单,创建枚举本来就是线程安全的,还能防止反序列化导致重新创建新的对象,直接通过Singleton.INSTANCE来访问实例。

总结

第一种方法是非线程安全的,不能算正确的写法,有些书上就是这么写的误人子弟啊。 其他方法根据实际情况来选择。
一般情况下使用饿汉式写法就可以了,除非有明确要求要用延迟加载(Lazy Loading),这时候可以考虑另外几种实现方法。

文章目录
  1. 1. 单例模式介绍
  2. 2. 单例模式UML图:
  3. 3. 实现方式:
    1. 3.1. 懒汉式,非线程安全
    2. 3.2. 懒汉式,线程安全
    3. 3.3. 饿汉式
    4. 3.4. 双重检验锁(DCL)
    5. 3.5. 静态内部类
    6. 3.6. 枚举
  4. 4. 总结
Fork me on GitHub