单例模式的七种写法

Posted by xdshent on April 8, 2020

同一个JVM中保证实例的唯一性

饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class Singleton {
    
    private static Singleton instance = new Singleton();
    
    private Singleton(){
        // 私有化构造函数
    }
    
    public static Singleton getInstance(){
        
        return instance;
    }
}
  • 优点:JVM在类加载初始化阶段<clinit>()会保证INSTANCE 的唯一性。
  • 缺点:无法进行懒加载,可能很长时间才被使用。

懒汉式

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

    private Singleton() {
        // 私有化构造函数
    }

    public static Singleton getInstance() {

        if (instance == null) {
            instance = new Singleton();
        }

        return instance;
    }
}
  • 优点:可以懒加载。
  • 缺点:多线程情况下不保证INSTANCE的唯一性。

懒汉式+同步方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final class Singleton {

    private static Singleton instance;

    private Singleton() {
        // 私有化构造函数
    }

    public static synchronized Singleton getInstance() {

        if (instance == null) {
            instance = new Singleton();
        }

        return instance;
    }
}
  • 优点:既可以保证单实例又可以实现懒加载。
  • 缺点:synchronized加在方法上多线程的情况下效率低。

Double Check Lock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public final class Singleton {

    private static Singleton instance;

    private Singleton() {
        // 私有化构造函数
    }

    public static Singleton getInstance() {

        if (instance == null) {
            synchronized (Singleton.class){
                if (instance == null){
                  
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}
  • 优点:解决synchronized多线程情况下效率低的问题。
  • 缺点:多线程在获取实例的时候有可能得到的是一个未初始化完成的对象。

Volatile+DCL

创建一个对象分为三部:

  • 分配内存
  • 初始化对象
  • 把对应内存空间地址赋给相关引用

问题:由于JVM的重排序以及Happens-Before规则,以上三步并不一定按顺序发生,有可能2和3先后顺序发生变化就会出现获得一个还未初始化完成的对象。

解决:加上volatile 关键字禁止指令重排序。

volatile 保证可见性和禁止指令重排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final class Singleton {

    private static volatile Singleton instance;

    private Singleton() {
        // 私有化构造函数
    }

    public static Singleton getInstance() {

        if (instance == null) {

            synchronized (Singleton.class){
                
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}
  • 优点:解决以上所有缺点。
  • 缺点:写法复杂。

内部类

由JVM类加载初始化阶段(<clinit>())保证实例的唯一性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final class Singleton {

    private static volatile Singleton instance;

    private Singleton() {
        // 私有化构造函数
    }

    private static class Holder{

        private static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {

        return Holder.instance;
    }
}
  • 既保证多线程的情况下线程安全又实现懒加载以及写法便捷。

枚举方式

枚举的本质是一个final类。

1
2
3
4
5
6
7
8
9
public enum  Singleton {

    INSTANCE;

    public static Singleton getInstance(){

        return INSTANCE;
    }
}
  • 优点:由JVM保证实例的唯一性,也是《Effective Java》作者推荐的方式。
  • 缺点:无法懒加载。

枚举改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

    private Singleton() {
        // 私有化构造函数
    }

    private enum EnumHolder {
        INSTANCE;

        private Singleton SINGLETON;

        EnumHolder() {
            
            SINGLETON = new Singleton();
        }

        private Singleton getInstance() {

            return SINGLETON;
        }
    }

    public static Singleton getInstance() {

        return EnumHolder.INSTANCE.getInstance();
    }
}
  • 由JVM保证实例的唯一性并且可以懒加载 。