java 单例 dcl_DCL单例到底需不需要volatile修饰

本文探讨了DCL单例模式下使用volatile关键字的重要性。通过分析类实例化过程及指令重排序的问题,揭示了如果不使用volatile可能会导致的单例对象初始化不完全的问题。

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

ba5c488eb944c3990780caf0f3f1b445.png

我们先来回答这个问题 DCL单例一定需要volatile修饰。

volatile有两个功能

内存可见

防止指令冲排序

这里主要考察volatile第二个功能。在介绍为什么DCL单例一定需要volatile修饰之前,我们先来看一下DCL单例和类实例化过程。

DCL单例

public class SingleExample {

private static volatile SingleExample singleExample = null;

private Long count = 99;

private SingleExample(){}

public static SingleExample getInstance(){

if(singleExample == null){

synchronized (SingleExample.class){

if(singleExample == null){

singleExample = new SingleExample();

}

}

}

return singleExample;

}

}

类实例化过程

这里以TestProcess为例来看一下Java对象实例化需要经过哪些步骤。

代码如下:

public class TestProcess {

public Integer num = 9;

public static void main(String[] args){

TestProcess testProcess = new TestProcess();

}

}

main方法执行完后我们来看一下生成的字节码

4c8390e3ee064d1d939a09c43aa26f8c.png

上面的字节码命令,我们只需要关注如下几行:

0 new #2

4 invokespecial #3 >

7 astore_1

下面我们来解释这三行的意思

//内存中分配空间,,同时为成员变量赋 '零值',即 num = null

命令1: 0 new #2

// 执行TestProcess类的构造方法

命令2: 4 invokespecial #3 >

// 将testProcess变量与内存生成的对象建立链接

命令3: 7 astore_1

场景解释

好了,解释完对象生成的关键这三步,我们回到主题上,我们来举例个场景来说明为什么DCL单例需要volatile修饰。

现在有两个线程

thread1 拿到了SingleExample类锁,正在实例化对象,并且刚执行完命令1 (此时count = null),将要执行命令2和命令3的时候,cpu发生了指令重排序,此时先执行了命令3。

此时 thread2,执行 if(singleExample == null),发现singleExample 不为null,,返回了singleExample对象。

对于thread2来说singleExample对象是没有意义的,因为该对象并未完成真正的初始化,成员变量count的值是null而不是99。如图所示:

3502f18840fe77db532511d19d0a3f80.png

至此DCL单例到底需不需要volatile修饰问题讲解结束,欢迎大家轻踩。

### 模式中的双重检查锁定(DCL)实现 在模式中,双重检查锁定(Double-Checked Locking, DCL)是一种常见的优化技术,用于确保线程安全的同时避免不必要的同步开销。以下是基于引用内容和专业知识对DCL实现的详细说明。 #### 1. 基本实现 双重检查锁定的核心思想是在访问实时进行两次非空检查,以确保线程安全并减少锁的使用频率。以下是一个典型的DCL实现[^2]: ```java package com.geek.singleton.lazy.dcl; public class Singleton { private static volatile Singleton instance; // 使用volatile关键字 private Singleton() {} // 私有构造函数 public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { // 加锁 if (instance == null) { // 第二次检查 instance = new Singleton(); // 创建实 } } } return instance; } } ``` #### 2. 关键点解析 - **`volatile`关键字**: 在上述代码中,`volatile`关键字被用来修饰`instance`变量,以确保多线程环境下的可见性和有序性。这是因为`new`对象的操作并非原子性的,可能会导致指令重排序问题,从而使其他线程看到一个未完全初始化的对象[^2]。 - **两次非空检查**: 第一次检查是为了避免不必要的同步操作,提高性能;第二次检查是在加锁后再次确认实是否已经被创建,从而确保线程安全。 - **`synchronized`块**: 只有在`instance`为`null`时才会进入同步块,因此减少了锁的竞争,提升了效率。 #### 3. 工作原理 当多个线程同时调用`getInstance()`方法时,可能会发生以下情况: - 如果`instance`已经存在,则直接返回实,无需进入同步块。 - 如果`instance`尚未创建,则通过同步块确保只有一个线程能够创建实,其余线程等待或直接获取已创建的实。 #### 4. 注意事项 - **JVM内存模型**: `volatile`关键字确保了`instance`的写入操作对所有线程可见,并禁止指令重排序,从而避免了部分初始化的问题[^2]。 - **线程安全性**: DCL模式在Java 5及以上版本中是线程安全的,因为`volatile`关键字的行为在JMM(Java Memory Model)中得到了明确规定。 #### 示测试 以下是一个简的测试代码,验证DCL实现的正确性: ```java public class DCLTest { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance == instance1); // 输出 true } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值