volatile 从单例dcl说起

探讨了单例模式中的双重检测机制,并分析了在多线程环境下可能导致的空指针异常问题。深入讲解了Java对象创建过程中的指令重排问题,以及如何使用volatile关键字来确保线程安全。

先看一段简单的代码,单例的双重检测机制.
public class Singleton {
    //双重锁
    private  static Singleton singleton;//未加volatile 关键字,线程不安全

    private Singleton(){
        System.out.println("当前线程:"+Thread.currentThread().getName());
    }

    public static Singleton getInstance(){
        if(singleton == null){  //步骤一
            synchronized (Singleton.class){//同步
                if(singleton == null){//步骤二
                    singleton = new Singleton(); //步骤三
                }
            }
        }
        return singleton;
    }

}

细心的同学会发现,在定义静态变量singleton的时候,没有用到volatile关键字修饰,而在测试中貌似也不会出现线程安全问题,始终会只有一个对象。如果单单是上面这段代码,貌似不会出现太大的问题,因为只是建了一个对象。但若在新建对象的时候,在调用构造方法时还有其他操作,比如新建socket连接,或者新建数据库连接,可能就会出现报null,空指针异常问题,很奇怪,明明已经在新建单例的时候加了同步方法,为什么还可能取到空对象。原因就在于 步骤3,,即singleton = new Singleton(); 
java中对象的创建,可以分为如下几个步骤,当然,前提是类已经加载了。
1.为新建对象分配内存空间。
2.调用构造函数初始化对象。
3.将引用指向已分的内存空间。
其中,步骤2,3,可能会出现指令重排的情况。
试想,如果new的过程变成了1-3-2,当线程T1执行到3的时候(此时,其实只是引用指向了一块空的内存空间,对象并初始化,至于什么时候初始化,对于单线程来说,只要后续有调用对象的方法,那肯定是已经初始化完成了),另一个线程T2刚好执行到步骤一,此时,singleton 其实不为空,但是对象却并未初始化。T2随即会返回一个空对象,从而引起了不安全创建单例对象。
而解决的办法,即是给singleton 加上volatile关键字,防止指令重排。

而volatile关键字的作用,一般书中都会说到:有序性(防止指令重排),可见性(工作内存缓存行无效机制),无法保证原子性(区别于synchronized)。

### Java中`volatile`关键字在模式中的作用及其实现原理 #### 1. `volatile`的关键特性 `volatile`关键字的主要功能在于提供**线程间的可见性****禁止指令重排**。这意味着当一个线程修改了一个被标记为`volatile`的变量时,这种修改会立即对其他线程可见[^1]。 #### 2. 模式中的问题分析 在懒汉式模式下,通常存在两个主要问题: - **线程安全性**:如果多个线程同时访问实化部分,则可能导致创建多个实。 - **指令重排**:JVM可能会为了优化性能而调整代码执行顺序,从而引发潜在问题[^3]。 具体来说,在以下代码片段中: ```java public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 双重检测锁机制的第一层判断 synchronized (Singleton.class) { if (instance == null) { // 第二层判断 instance = new Singleton(); } } } return instance; } } ``` 尽管使用了双重检测锁定(Double-Checked Locking),但由于`new Singleton()`不是一个原子操作,实际上它分为三步完成: 1. 分配内存空间; 2. 初始化对象; 3. 将引用指向分配的对象地址。 如果没有`volatile`修饰符,编译器可能重新排列这些步骤,使得第三步发生在第二步之前。此时另一个线程可能看到未完全初始化的对象,进而抛出异常或导致不可预测的行为[^3]。 #### 3. 使用`volatile`解决上述问题 通过将`instance`声明为`volatile`,可以有效防止此类情况的发生。因为`volatile`不仅能够保证不同线程间数据的一致性,还能阻止发生任何涉及此字段的操作序列被打乱的情况。因此,即使是在多核处理器环境下运行程序也不会出现问题[^5]。 以下是改进后的版本: ```java 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; } } ``` 在此基础上,由于`volatile`的存在,“instance=new Singleton();”这条语句不会因CPU缓存等原因造成错误的结果显示给其它正在工作的进程/线程去读取尚未准备完毕的数据结构体成员值[^4]。 #### 4. 性能考量 虽然引入同步块解决了基本的安全隐患,但如果频繁调用`synchronized`则会影响整体效率。利用`volatile`配合双检锁技术可以在一定程度上缓解这个问题,因为它允许无竞争条件下快速返回已存在的唯一实,只有第一次创建时才需加锁保护[^5]。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值