java 单例模式中的双重检测为什么要加 volatile 关键字?
  VDvlWkTw2thq 2023年11月19日 26 0

Java 实现单例模式有方法有双重检测锁,代码如下:

public class Singleton {
    private static volatile Singleton singleton = null;

    private Singleton(){}

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

我理解的 synchronized 关键字实现了可见性、原子性和有序性,临界区中的代码可以重排序,但是不能重排序到临界区外面,synchronized 实现的可见性是临界区中代码执行结束之后,里面的共享变量会刷新到主内存中,那么如果 new Singleton() 方法被拆成了三个操作,并且经过重排序之后的顺序是这样的话:

  1. 分配内存
  2. 将实例引用赋值给 singleton 变量
  3. 实例初始化

不管这三个操作怎么重排序,另外一个线程看到的结果都是这三个操作执行完成后的结果(因为 synchronized 的原子性),那不就相当于另外一个线程访问到的 singleton 如果不为 null 的话就肯定实例化了吗?为什么还要多此一举加个 volatile 关键字禁止重排序呢?

在双重检测锁的单例模式实现中,将 singleton 变量标记为 volatile 是为了解决指令重排序问题。

指令重排序是指处理器在执行指令时可能会对指令进行重新排序,以提高执行效率。然而,由于多线程环境下的指令重排序可能导致单例对象的不正确初始化,因此需要使用 volatile 关键字来禁止重排序。

具体而言,在没有使用 volatile 的情况下,可能会发生以下情况:

  1. 线程A进入 getSingleton 方法中,发现 singleton 为null,因此进入同步块中。
  2. 线程A在同步块中执行了分配内存、将实例引用赋值给 singleton 变量和实例初始化等操作,但是这些操作可能会被重排序。
  3. 在还没有完成实例初始化的情况下,线程B进入 getSingleton 方法,发现 singleton 不为null(由于线程A的部分操作可能已经执行完成),于是直接返回 singleton
  4. 线程B尝试使用 singleton,但由于实例初始化的某些操作尚未完成,可能会导致错误。

通过将 singleton 声明为 volatile,可以确保在每个线程中,所有的写操作(分配内存、初始化等)都发生在对 singleton 的读操作之前。这样就可以避免上述的问题。

综上所述,使用 volatile 关键字可以确保双重检测锁的单例模式在多线程环境下正确地实现延迟加载和单例对象的正确初始化。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月19日 0

暂无评论

推荐阅读
git
  KRsXEGSB49bk   2023年11月19日   20   0   0 git远程仓库初始化
  6RHESoY2tEQV   2023年11月02日   69   0   0 初始化rediscentos
  L83A5jZvvg3Q   2023年11月19日   25   0   0 vim初始化物理内存
VDvlWkTw2thq