followyourheart
V2EX  ›  Java

单例模式 双检测问题请教

  •  
  •   followyourheart · Apr 11, 2022 · 4938 views
    This topic created in 1668 days ago, the information mentioned may be changed or developed.
    public class Singleton {
    
        private volatile static Singleton uniqueInstance;
        
        private Singleton(){};
        
        public static Singleton getInstance(){
            if(uniqueInstance == null){
                synchronized(Singleton.class){
                    if(uniqueInstance == null){
                        uniqueInstance = new Singleton();
                    }
                }
            }
            return uniqueInstance;
        }
    }
    

    uniqueInstance = new Singleton() 这行代码虽然会发生指令重排序, 但 synchronized 代码块已经加锁了,每次只有一个线程进入代码块,为啥还要加 volatile ?

    31 replies  •  2022-12-03 18:39:08 +08:00
    hay0577
    hay0577
    Apr 11, 2022
    1
    加了 synchronized 不代表一定只有一个线程进到了方法里面..之前有个视频里有讲过..

    链接: https://pan.baidu.com/s/1bb-6itr18pnILc6yZCdvyQ 提取码: fbpy 复制这段内容后打开百度网盘手机 App ,操作更方便哦
    jwh199588
    jwh199588
    Apr 11, 2022
    2
    防止重排序的问题,uniqueInstance = new Singleton();这里存在一个排序的问题
    Kaiv2
    Kaiv2
    Apr 11, 2022
    3
    https://cdmana.com/2022/03/202203090332315266.html

    这里讲的比较清楚
    double-checked locking
    xuyang2
    xuyang2
    Apr 11, 2022
    4
    都什么年代了,还用 `static Singleton instance` ...
    Suddoo
    Suddoo
    Apr 11, 2022 via iPhone
    5
    用 enum 吧,单例模式已经没意义了,甚至以前的那些设计模式都没有意义了,Java 语言是在不断演进的
    wolfie
    wolfie
    Apr 11, 2022
    6
    其他线程即时可见
    ewq
    ewq
    Apr 11, 2022
    7
    idea 里面在第一个 if 上面,按两次 ctrl + f1 ,会出现解释

    Double-checked locking
    Inspection info: Reports double-checked locking.
    Double-checked locking tries to initialize a field on demand and in a thread-safe manner while avoiding the cost of synchronization. Unfortunately it is not thread-safe when used on a field that is not declared volatile. When using Java 1.4 or earlier, double-checked locking doesn't work even with volatile fields. Read the article linked above for the detailed explanation of the problem.
    Example of an incorrect double-checked locking:
    class Foo {
    private Helper helper = null;
    public Helper getHelper() {
    if (helper == null)
    synchronized(this) {
    if (helper == null) helper = new Helper();
    }
    return helper;
    }
    }
    // other functions and members...
    }
    MapHacker
    MapHacker
    Apr 11, 2022
    8
    加锁之前的那个 if 会出问题,直接等于 false 拿到一个未初始化完毕的 instance
    pennai
    pennai
    Apr 11, 2022
    9
    如果不加 volatile ,取得锁进行初始化的线程对变量的更新操作不一定能及时地被其他线程感知,其他线程有可能还是会判断 uniqueInstance == null 为 true ,volatile 是保证了可见性,invalidate 了其他线程工作内存的变量副本。

    "普通变量与 volatile 变量的区别是,volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此我们可以说 volatile 保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。"
    出自深入理解 java 虚拟机第三版
    mxalbert1996
    mxalbert1996
    Apr 11, 2022 via Android
    10
    不加 volatile 也不会有错但是加了 volatile 可以在其他线程已经生成实例时直接返回,避免同步,所以性能会更好。
    liudaolunhuibl
    liudaolunhuibl
    Apr 11, 2022
    11
    这里加 volatile 不是为了防止指令重排是为了线程之间的可见性
    kbac001
    kbac001
    Apr 11, 2022
    12
    @Suddoo 啊?能问问原因吗,我平常还挺经常用的
    Leviathann
    Leviathann
    Apr 11, 2022
    13
    两线程,一个判断完第一个 null ,另一个已经拿完锁创建并返回了
    那不加 volatile 第一个线程接下来进到同步块里的那个判空不是就无效了吗,直接从工作区取的,和外层的判空完全一致了
    fantastM
    fantastM
    Apr 11, 2022
    14
    @Suddoo #5 这段 double-checked 单例模式是懒加载的,和 enum 还是有点区别的
    praxis
    praxis
    Apr 11, 2022❤️1
    15
    uniqueInstance = new Singleton(); 会被编译成三条计算机指令
    1 、为 uniqueInstance 分配一个内存地址 A
    2 、在内存地址 A 上初始化 uniqueInstance 实例
    3 、把内存 A 的地址赋值给 uniqueInstance 变量
    如果不禁止指令重排 可能导致 顺序变为 1 、3 、2
    这样的话 当一个线程执行到 1 、3 得到的是一个未初始化的 uniqueInstance 而另一个线程执行到第一个 if 就会返回一个未初始化的 uniqueInstance
    golangLover
    golangLover
    Apr 11, 2022 via Android
    16
    @xuyang2 @Suddoo 为什么这么说
    fkdog
    fkdog
    Apr 11, 2022❤️1
    17
    两个线程,假如 uniqueInstance 非 volatile 。
    0:00 ,A 刚进入方法内,uniqueInstance 此时应该是从内存里 copy 了到线程的工作内存里。

    0:01 ,B 刚离开同步块,B 完成 uniqueInstance 的初始化,将本地工作线程里的 uniqueInstance 同步回主内存。
    由于 uniqueInstance 非 volatile ,A 线程无法感知 B 线程种的同步变化,因此 A 会继续走剩余的逻辑进入同步块。由于同步块里会将 uniqueInstance 变量进行同步,同步完会发现 uniqueInstance 非空,因此需要重新判断一次非空来保证 uniqueInstance 不会被重复初始化。

    如果 uniqueInstance 是 volatile ,那么 A 可以感知到 uniqueInstance 的变化,从而避免进入同步块降低吞吐。

    总结:
    1. 代码进入同步块以后,uniqueInstance 可能已经发生变化,多加一层 null 判断是为了防止重复初始化。
    2. 加 volatile 是为了防止代码进入不必要的同步块,提高性能。
    TWorldIsNButThis
    TWorldIsNButThis
    Apr 11, 2022❤️1
    18
    @XieQing0428
    有很多 design pattern 是为了给 java 、C++这帮 oop 的语言当年没有函数这个抽象擦屁股
    搜 design pattern functional programming
    当然 fp 语言有自己的 pattern ,比如 monad
    另外随着 java pattern matching 能力的到来,Visitor Pattern Considered Pointless (这个是 oracle java 团队成员的一篇博文)
    Suddoo
    Suddoo
    Apr 11, 2022 via iPhone
    19
    2022 年了,还是会有傻逼面试官问设计模式,就单例讲出一堆“底层”原理。时代变了,明明有了自动挡汽车,非要开手动挡
    Suddoo
    Suddoo
    Apr 11, 2022 via iPhone
    20
    @TWorldIsNButThis 是这样的,因为当时 Java 语言还不完善,才弄出这么多补救措施,但现在,很多设计模式已经没有存在的必要了
    BQsummer
    BQsummer
    Apr 11, 2022 via Android
    21
    @Suddoo 自动挡汽车指的是什么
    git00ll
    git00ll
    Apr 11, 2022
    22
    a 线程使用赋值语句赋值后,b 线程可能看不到
    crackhopper
    crackhopper
    Apr 11, 2022
    23
    每次看到 volatile 就头疼。C++和 Java 的 volatile 还不一样。
    liudaolunhuibl
    liudaolunhuibl
    Apr 11, 2022
    24
    @BQsummer 用枚举吧,话说这种手写单例模式的还怎么见过,一般你需要某个类单例就把它交给他 spring 就好了
    C02TobNClov1Dz56
    C02TobNClov1Dz56
    Apr 11, 2022
    25
    推荐用静态内部类的写法代替这种写法, 或者用枚举, 最好用 spring 直接管理.
    ligiggy
    ligiggy
    Apr 11, 2022
    26
    @crackhopper 哈哈哈,太经典了
    zhady009
    zhady009
    Apr 11, 2022
    28
    要延迟加载用内部静态类的方式就好了 这种写法不知道多少年前的
    如果不用延迟加载用 static final 或者直接 enum
    MakHoCheung
    MakHoCheung
    Apr 11, 2022
    29
    同意 25 楼,与其深究这种容易出问题需要了解底层的方式,还不如去掌握没问题的方式
    Red998
    Red998
    Apr 15, 2022
    30
    是对象初始化有几个步骤、申请内存--初始化--赋值--返回内存引用地址 。多线程情况下可能会存在 获取半初始化对象 。所有才需要 volatile 防止指令冲排序 至于为什么 cpu 指令会这样。个人觉得效率高吧。无论怎么样执行最后都是返回一个对象引用地址。 不过话又说回来 我真没看到现在有这么用 。一般的单例用的多也没啥大问题。非要完美一点 我觉得那个枚举方式就很好
    goalidea
    goalidea
    Dec 3, 2022
    31
    没有必要搞这么细,这也就面试中可能会问一问,工作中都是懒汉,不然就内部类实现
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   2404 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 88ms · UTC 15:40 · PVG 23:40 · LAX 07:40 · JFK 10:40
    ♥ Do have faith in what you're doing.