单例模式的五种Java实现

定义

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

特点

  • 单例类仅有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
  • 单例类的唯一实例必须由自己创建
  • 避免对共享资源的多重占用
  • 单例对象在堆上分配的内存空间只有在程序终止后才会释放(不要滥用单例)

使用场景

  • 需频繁实例化/销毁的对象
  • 有状态的工具类对象
  • 频繁访问数据库/文件的对象

实例

  • 网站的计数器采用单例模式,以保证同步。
  • windows的Recycle Bin(回收站)是典型的单例应用。
  • 应用程序的日志应用一般用单例模式实现。由于共享的日志文件一直处于打开状态,只能有一个实例去操作,否则内容不好追加。
  • 数据库连接池一般也是采用单例模式。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗
  • 多线程的线程池一般采用单例模式

饿汉模式

饿汉模式指的是,使用之前初始化,类加载时就完成了初始化

特点

  • 类加载比较慢,会占用较多的内存
  • 基于classloader的机制避免了同步问题
  • 由于没有使用锁,执行效率高

代码

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

懒汉式 Lazy Initialization

指的是在用的时候才实例化,即懒加载。

特点

  • 需要使用锁来保证线程安全,这会牺牲效率

代码

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

双重检查模式

  1. 第一次检查
    • 避免没必要的同步,如果不是null的话,线程不会被阻塞,直接返回单例。
    • 只有在单例是null的情况下,需要同步构建一个单例
  2. 第二次检查

    由于进行了同步,在第一个进入临界区的线程建立单例后,为了防止后面其他被阻塞的线程重复创建单例,因此进行第二次检查:不为null就直接返回。

  3. volatile保证有序性

    由于singleton=new Singleton()语句不具有原子性,该语句可分为三步:

    • 分配内存空间
    • 初始化对象
    • 将内存空间的地址赋给对应的引用

      有可能指令重排后,初始化对象放在了最后一步,而此时singleton已经不是null了,因此可能有其他的线程成功跳过了第一次检查,返回了一个没有正确初始化的实例。
      而volatile可以禁止指令重排,保证所有线程都可以获取到正确实例化的单例对象。

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

静态内部类模式

推荐使用

可以达到与DCL同样的效果,而且实现简单。

  • 同步问题:利用了ClassLoader的机制避免了同步问题(classloader可以保证一个类只被加载一次)
  • 懒加载:而静态内部类只在被调用的时候,才会被加载,因而保证了懒加载
1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton { 
// Holder
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

private Singleton (){}

public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}