Featured image of post 单例模式

单例模式

单例模式所要实现的目标:保持一个类有且只有一个实例。

出于性能的考虑,不少单例模式都会采用延迟加载的方式。根据是否延迟创建对象,可以分为:

  • 🤩 饿汉式

  • 😪 懒汉式

😪 懒汉式

单线程的单例模式

 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 = null;

    // 构造器私有,其他类无法通过 new 初始化
    private Singleton() { }

    // 创建并返回该类的唯一实例
    public static Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();
        }
    }

    public void someService() {
        // 一些业务代码
    }
}

🔒 加锁的单例模式

使用 synchronized 加锁实现线程安全。

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

    private Singleton() { }
    
    // public static synchronized getInstance()
    public static Singleton getInstance() {
        synchronized (Singleton.class) {  // 不推荐 🙅‍♂️ 每次判空操作,都要获取锁,开销极大
            if (null == instance) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

使用双重检验判断

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

    private Singleton() { }

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

使用 volatile 修饰实例,利用了以下两个作用:

(1) 保障可见性:

一个线程初始化了 instance 的值,其他线程可以读取到相应的值。

(2) 保障有序性:

保障一个线程读取到的 instance 引用的实例已经初始化完毕。

对象实例化会分解成以下几个子操作:

  • 分配对象所需的空间
  • 初始化 instance 引用的对象
  • 将对象的引用写入共享变量

由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。

例如:线程 T1 执行了 1 和 3 后,此时 T2 调用 getInstance() 后发现 instance 不为空,因此直接返回,但此时 instance 还未被初始化。

静态内部类的单例模式

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

    private static class InstanceHolder {  // 静态内部类
        // 保存外部类的唯一实例
        private final static Singleton INSTANCE = new Singleton(); 
    }

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

    public void someService() {
        // ...
    }

    public static void main(String[] args) {
        Singleton.getInstance().someService();
    } 
}

当外部类加载时,内部类不会加载,并且静态变量只会有默认值(null)。

类的静态变量被初次访问才会触发 Java 虚拟机对该类进行初始化,会变为其初始值。

枚举单例

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

    Singleton() {  // 私有构造器
    }

    pubilc void someService() {
        // 业务代码
    }
}

字段 INSTANCE 是枚举类的唯一实例。在 Singleton.INSTANCE 初次被引用时才会被初始化。

🤩 饿汉式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pubilc final class Singleton {
    // 在创建时直接初始化,天生线程安全
    private static Singleton instance = new Singleton();

    println Singleton() { }

    pubilc staitc Singleton getInstance() {
        return instance;
    }
}