设计模式篇-单例模式

引言

单例模式是创建型模式中最简单的一种,它只允许我们创建类的唯一实例。

实现

首先要创建类Singleton唯一实例,我们不希望外部能通过new的形式实例化它,所以需要私有的构造器。私有的构造器怎么创建实例,以及对外发布呢?使用静态域很好的解决了这个问题。

急切初始化

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

INSTANCE是静态的,INSTANCE在类加载时就创建了实例。如果你不想这么急切的创建实例,希望使用时在创建,可以采用下面的方式。

延迟初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
@NotThreadSafe
public class Singleton{
private static Singleton INSTANCE;
private Singleton(){
}
public static getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
}

在单线程中,这个单例完美运行。而在多线程中,由于存在“先检查后运行”的竞态条件,多线程可能会创建出多个实例,也就破坏了我们的单例。
怎么解决?简单粗暴一点,直接在getInstance使用内置锁synchronized,当且仅当同步降低的性能不是你在意的。
怎么不失性能,又能完美保证单例的线程安全?

“双重检查”(Double Check Lock)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ThreadSafe
public class Singleton{
private volatile static Singleton INSTANCE;
private Singleton(){
}
public static getInstance(){
if(INSTANCE == null){
synchronized(Singleton.this){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
}

双重检查加锁,俗称DCL。java1.5之前,许多JVM对于volatile的实现会导致DCL失效。java1.5之后,DCL方式还是可取的。

推荐方式

  1. 通过内部类创建单例(推荐)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @ThreadSafe
    public class Singleton{
    //内部类只在调用getInstance时才加载和初始化静态域
    private static class SingletonHolder{
    public static Singleton = new Singleton();
    }
    public static Singleton getInstance(){
    return SingletonHolder.Singleton();
    }
    }

    这种方式创建单例,即做到了延迟初始化,又保证了线程安全,推荐使用该方式。

  2. 使用枚举

    1
    2
    3
    4
    @ThreadSafe
    public enum Singleton{
    INSTANCE;
    }

枚举也是不错的选择,也同样做到了延迟初始化和线程安全。
enum延迟初始化详解

小结

什么是单例模式

确保一个类只有一个实例,并提供一个全局的访问点。

优点

  1. 使用单例,避免了大量对象的创建和销毁,节约了系统资源,提升性能。
  2. 全局唯一,提供全局访问。

缺点

  1. 单例无法被继承,扩展性较差。
  2. 单例即管理自己的实例,又参与具体的应用逻辑,违背“单一职责原则”。

适用场景

只需要一个对象,比如线程池,缓存,对话框,日志对象,序列号生成器等。

实际应用

  1. java.lang.Runtime
  2. java.awt.Desktop
  3. java.lang.SecurityManager