单例模式-饿汉、懒汉(线程安全、双重检查锁定)、静态内部类、枚举

1 饿汉式

这种方式是最简单的单例模式实现,它在类加载的时候就创建了单例对象。
优点

  • 实现简单
  • 线程安全
    缺点
  • 无论是否使用,都会加载单例类并创建单例对象
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

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

2 懒汉式

2.1 懒汉式(线程不安全)

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

2.2 使用同步保证线程安全

优点

  • 线程安全
  • 延迟加载
    缺点
  • 效率较低,每次调用 getInstance() 都会同步阻塞
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

2.3 懒汉式(双重检查锁定)

这种方式使用双重检查锁定来减少同步开销。
优点

  • 线程安全
  • 延迟加载
  • 效率较高
public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {}

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



解析
第一步:初次检查 instance 是否为 null
if (instance == null) {

  1. 初次检查:
  • 在进入 synchronized 块之前,先检查 instance 是否为 null。
  • 如果 instance 已经被初始化,那么就不需要再次初始化,可以直接返回已存在的实例。
  • 这个检查可以避免在每次调用 getInstance() 方法时都进行同步操作,提高了性能。

第二步:同步 Singleton.class 对象
synchronized (Singleton.class) {
2. 同步锁:

  • 使用 synchronized 关键字锁定 Singleton.class 对象。
  • 这里使用 Singleton.class 作为锁对象是因为它是静态的,并且在整个应用程序生命周期中是唯一的。
  • 当一个线程获取到这个锁时,其他试图获取相同锁的线程将被阻塞,直到锁被释放。
  • 这样可以确保在任何时候只有一个线程能够进入这个 synchronized 块。

第三步:二次检查 instance 是否为 null
if (instance == null) { instance = new Singleton(); } }
3. 二次检查:

  • 再次检查 instance 是否为 null。
  • 这一步是为了确保在其他线程已经初始化了 instance 的情况下,当前线程不会重复初始化。
  • 这个检查非常重要,因为如果没有这个检查,可能会发生以下问题:
    • 多次创建实例
      • 如果多个线程几乎同时进入 synchronized 块,它们可能会同时执行 instance = new Singleton(),导致创建多个 Singleton 实例,违反了单例模式的要求。
    • 指令重排序导致对象不完整
      • JVM 或编译器可能会为了优化而重排指令顺序。例如,instance = new Singleton(); 可能会被重排为:
        a. 分配内存给 instance。
        b. 调用 Singleton 的构造函数初始化 instance。
        c. 将 instance 设置为指向新分配的内存地址。
      • 如果两个线程几乎同时进入 synchronized 块,其中一个线程完成了第 1 步和第 2 步,但还没有完成第 3 步,这时另一个线程进入 synchronized 块并执行了 new Singleton(),那么第二个线程可能会看到一个尚未完全构造好的 Singleton 实例。
    • 可见性:
      • 如果没有 synchronized 块,那么一个线程创建了 instance 并将其设置为非 null 后,其他线程可能看不到这个更新,因为 Java 内存模型允许线程缓存变量的值。
      • synchronized 块保证了 instance 的更新对所有线程都是可见的。

3 静态内部类

这种方式利用 Java 的类加载机制来保证初始化实例时只有一个线程。
优点

  • 线程安全
  • 延迟加载
  • 效率较高
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

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

4 枚举

利用枚举类型的特性来实现单例。
优点

  • 线程安全
  • 延迟加载
  • 防止反射攻击
  • 防止序列化攻击
public enum Singleton {
    INSTANCE;

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

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值