单例模式所要实现的目标:保持一个类有且只有一个实例。
出于性能的考虑,不少单例模式都会采用延迟加载的方式。根据是否延迟创建对象,可以分为:
😪 懒汉式
单线程的单例模式
|  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;
    }
}
 |