单例模式(双重锁+volatile)

本文详细解析了Eureka 1.7.2版本中ConfigurationManager类的单例模式实现方式,特别是对双重检查锁定及volatile关键字的作用进行了深入探讨。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在学习Eurake源码(版本为1.7.2),看到源码里面有关于单例模式的,特地记录学习一波。

这个代码是在com.netflix.config.ConfigurationManager类里面,下面是部分代码:

    static volatile AbstractConfiguration instance = null;
    public static AbstractConfiguration getConfigInstance() {
        if (instance == null) {
            synchronized (ConfigurationManager.class) {
                if (instance == null) {
                    instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG));
                }
            }
        }
        return instance;
    }

直接调用ConfigurationManager.getConfigInstance()方法获取AbstractConfiguration对象,下面分析下单例模式(双重锁+volatile)

场景是并发情况下,多个线程同时执行到ConfigurationManager.getConfigInstance()方法,可以看到这个方法上面是没有synchronized关键字.

1.发线程都会进入方法内部,此时脑海中会冒出一副脑图:

 2.假设线程1先拿到锁,判断instance == null,判断结果为true,执行

instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG));

方法,此时instance已经被初始化了,此时没有加volatile修饰的内存脑图如下:

假如此时没有instance 引用前面没有volatile修饰,只会在线程1内部完成初始化,对于线程2来说,instance还是=null,线程2还是存在一定的概率把instance在一次初始化一遍。

以上纯属幻想,我们这里instance引用前面是有volatile修饰的,加了volatile修饰实际脑图为:

 

volatile关键字的作用得看自己实力领悟了,其中一个作用就是会把其装饰的引用对象立即刷回主内存,并且将其他线程引用(线程2)置为无效。当线程2获取到锁,这个时候判断instance已经不等于null了,判断结果为false,所以不会进行下一步的初始化了。进而达到单例的控制。

最后的最后,加油,加油,加油!!!

### 单例模式双重的实现方式 在 Java 中,单例模式是一种常见的设计模式,用于确保某一个类只有一个实例存在,并提供全局访问点。为了支持多线程环境下的懒加载机制,通常采用 **双重检查定(Double-Checked Locking, DCL)** 来实现单例模式。 以下是基于双重检查定的单例模式实现及其解析: --- #### 代码示例 ```java public class Singleton { // 声明为 volatile 防止指令重排序 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; } // 测试方法 public void showMessage() { System.out.println("This is the singleton instance."); } } ``` --- #### 关键点解析 1. **`volatile` 关键字的作用** - 在多线程环境下,JVM 可能会对指令进行优化,导致 `new Singleton()` 的三步操作(分配内存空间、初始化对象、设置引用变量指向新建的对象)发生重排序。 - 如果没有使用 `volatile`,可能会出现某个线程读取到未完全初始化的对象的情况[^5]。 - 使用 `volatile` 后,能够保证可见性和有序性,避免因指令重排序引发的问题[^3]。 2. **两次 `if` 判断的意义** - 第一次判断:如果不为空,则直接返回实例,无需进入同步块,从而提高性能。 - 第二次判断:即使进入了同步块,仍需再次确认是否已创建实例,因为在多个线程竞争的情况下,可能存在另一个线程已经完成了实例的创建[^4]。 3. **`synchronized` 定范围** - 定的是类本身 (`Singleton.class`),而不是具体的对象实例。 - 这样可以确保在同一时刻只有唯一的一个线程能够进入同步块执行实例化的逻辑[^1]。 4. **线程安全性分析** - 当第一个线程进入同步块并开始实例化时,第二个线程会被阻塞在外层的 `if` 或者同步块内。 - 等待第一个线程完成实例化后,第二个线程重新检查 `instance` 是否仍然为 `null`,如果不是,则直接返回已有实例[^5]。 --- #### 示例测试 以下是一个简单的测试案例,验证双重检查定的正确性: ```java public class TestSingleton { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { Singleton s1 = Singleton.getInstance(); s1.showMessage(); }); Thread t2 = new Thread(() -> { Singleton s2 = Singleton.getInstance(); s2.showMessage(); }); t1.start(); t2.start(); t1.join(); t2.join(); } } ``` 运行结果表明,无论多少个线程尝试获取实例,最终只会有一个 `Singleton` 对象被创建。 --- ### 总结 通过以上实现可以看出,双重检查定能够在多线程环境下有效地保障单例模式的安全性,同时减少了不必要的同步开销。核心在于结合了 `volatile` 和双层条件判断的设计思路,使得该方案既高效又可靠[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值