线程安全的单例模式是否真的安全

本文探讨线程安全的单例模式在加锁优化后可能存在的问题,由于处理器和编译器的重排序优化,可能导致多线程环境下的线程不安全。通过分析对象创建的三个步骤,指出在特定情况下可能出现的错误。解决方案是使用volatile关键字避免重排序,确保线程安全。

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

线程安全的单例模式是否真的安全

上次我们谈了一些关于JAVA中锁级别的问题,这一次和大家聊一聊JAVA中常见的单例模式,并且指出常见的线程安全的单例模式所存在的一些问题,并对问题给出部分解答。
首先,单例模式是我们常用和常见的经典设计模式之一,但是由于在JAVA中,多线程问题往往会引起单例模式的水土不服,所以,我们就见到了线程安全的单例模式,首先一种单例模式如下所示:
public class SafeInitialization{
    private static Instance instance;

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

在上述代码中,我们对getInstance进行了加锁处理,但是synchronized将导致性能开销,如果存在多个线程频繁的调用getInstance方法,那么将会导致执行性能的下降,所以又出现的一种以“双重检查锁定”为原理的线程安全的单例模式,如下所述:

public class SafeInitialization{
    private static Instance instance;

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

如代码所示:如果第一次检查对象不为null,则直接返回对象引用,就不需要执行下面的加锁操作,就可以大大的减小性能损耗,这也是我们常见的线程安全的单例模式,但是其实事实是上面的优化是错误的。根本原因发生在对象创建过程中:

instance = new Instance();

为什么这里会有错误呢,原因是这样的:
首先,一个对象的创建需要经历一下三步:

memory = allcoate(); //1.分配对象的内存空间
initInstance(memory);//2.初始化对象
instance = memory;   //3.设置指针instance指向内存空间

其实如果上面三步顺序执行将不会出现问题,但是由于处理器和编译器存在重排序优化(具体内容会在之后的博客中做阐述),所以将会导致多线程之间的instance引用同步问题。比如下面一种情况就是重排序问题:

memory = allcoate(); //1.分配对象的内存空间
instance = memory;   //3.设置指针instance指向内存空间,注意这时对象并未被初始化
initInstance(memory);//2.初始化对象

所以当线程B来访问:if(instance == null)时,线程B拿到的是一个不为null的instance引用,所以会直接返回,当线程B使用instance引用做处理时,就会出现问题。
那么我们有没有什么方法去解决这个问题呢,答案当然是有的,今天我们只阐述原理较为简单的一种,第二种留待之后讲解。
其实修改很简单,如下:

public class SafeInitialization{
    private volatile static Instance instance;

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

只需要对instance加volatile锁定就可以了,可以这样理解initInstance(memory); 是在对memory 进行写操作,instance = memory; 是在对memory进行读操作,由于对象使用volatile进行了修饰,编译器和处理器对于volatile对象的先写后读是不允许进行重排序的,所以相当于是禁止了重排序问题,重排序问题解决之后就不会出现上面的问题了。所以就可以实现绝对安全的重排序。
事实上,上面的问题出现的几率并不大,因为这种重排序只会在少数的一些JIT编译器上发生,所以作为一个程序员,真的是必须不断学习啊。。。。。。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值