当前位置: 首页 > 知识库问答 >
问题:

java 单例模式中的双重检测为什么要加 volatile 关键字?

束研
2023-10-30

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 关键字禁止重排序呢?

共有2个答案

袁建木
2023-10-30

加 volatile 就是为了保证变量的赋值对所有线程可见,这点与指令重排序无关。你说“不管这三个操作怎么重排序,另外一个线程看到的结果都是这三个操作执行完成后的结果”,实际情况是,如果没有 volatile,每个线程都可能会操作自己的 singleton 副本,他们看到的结果可能不同。synchronize 不保证变量对其他线程可见

关飞翔
2023-10-30

在 Java 中,使用 volatile 关键字可以确保多线程间的可见性。

在上述示例中,如果 Singleton 类的构造函数被拆分成三个操作,即使 synchronized 关键字确保了原子性和可见性,但是由于 JVM 的内存模型,可能会存在重排序的情况。

考虑以下情况:

  1. 线程 A 进入临界区,singleton 为 null,线程 A 分配了内存,并将实例引用赋值给 singleton 变量。
  2. 线程 A 继续执行,实例初始化。
  3. 线程 A 退出临界区,将 singleton 的值刷新到主内存中。
  4. 线程 B 进入临界区,看到 singleton 不为 null,因此直接返回 singleton

在这个情况下,线程 B 没有看到 singleton 变量被赋值为新实例的操作,因此它获取的 singleton 可能不是最新的实例。

通过添加 volatile 关键字,可以禁止 JVM 进行重排序,确保了在多线程环境下,其他线程看到的 singleton 总是最新的实例。因此,双重检测锁加 volatile 关键字是为了确保在多线程环境下的可见性和禁止重排序。

 类似资料:
  • 本文向大家介绍Java双重检查加锁单例模式的详解,包括了Java双重检查加锁单例模式的详解的使用技巧和注意事项,需要的朋友参考一下 什么是DCL DCL(Double-checked locking)被设计成支持延迟加载,当一个对象直到真正需要时才实例化: 为什么需要推迟初始化?可能创建对象是一个昂贵的操作,有时在已知的运行中可能根本就不会去调用它,这种情况下能避免创建一个不需要的对象。延迟初始化

  • 问题内容: 在设计模式手册中,具有双重检查锁定的单例模式已实现如下: 我不明白为什么要使用它。使用不会 违反使用双重检查锁定(即性能)的目的吗? 问题答案: 真正的问题是可能在完成构造之前为其分配存储空间。将看到该作业并尝试使用它。由于使用的是的部分构造版本,因此导致失败。

  • 本文向大家介绍Java中的volatile关键字,包括了Java中的volatile关键字的使用技巧和注意事项,需要的朋友参考一下 volatile修饰符用于让JVM知道访问该变量的线程必须始终将其自身的变量私有副本与内存中的主副本合并。 访问易失性变量将同步所有在主存储器中缓存的变量副本。可变变量只能应用于对象类型或私有类型的实例变量。易失性对象引用可以为null。 示例

  • 问题内容: 今天的工作中,我遇到了volatileJava中的关键字。不太熟悉,我找到了这种解释。 鉴于该文章详细解释了所讨论的关键字,您是否曾经使用过它,或者是否曾见过可以正确使用此关键字的情况? 问题答案: volatile具有内存可见性的语义。基本上,volatile字段的值对所有读取器(尤其是其他线程)可见,在该字段上完成写操作之后。没有volatile,读者可能会看到一些未更新的值。 要

  • 本文向大家介绍Java里volatile关键字是什么意思,包括了Java里volatile关键字是什么意思的使用技巧和注意事项,需要的朋友参考一下 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。 Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机

  • 问题内容: 我阅读了一些有关该关键字的文章,但无法弄清其正确用法。您能否告诉我在C#和Java中应该使用什么? 问题答案: 对于C#和Java,“ volatile”告诉编译器一个变量的值一定不能被缓存,因为它的值可能会在程序本身范围之外改变。然后,如果变量“超出其控制范围”更改,编译器将避免可能导致问题的任何优化。