单例模式 双检测问题请教

2022-04-11 09:29:02 +08:00
 followyourheart
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 ?

3212 次点击
所在节点    Java
31 条回复
hay313955795
2022-04-11 09:35:55 +08:00
加了 synchronized 不代表一定只有一个线程进到了方法里面..之前有个视频里有讲过..

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

这里讲的比较清楚
double-checked locking
xuyang2
2022-04-11 09:40:12 +08:00
都什么年代了,还用 `static Singleton instance` ...
Suddoo
2022-04-11 09:41:23 +08:00
用 enum 吧,单例模式已经没意义了,甚至以前的那些设计模式都没有意义了,Java 语言是在不断演进的
wolfie
2022-04-11 09:42:55 +08:00
其他线程即时可见
qgs
2022-04-11 09:45:24 +08:00
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
2022-04-11 09:49:13 +08:00
加锁之前的那个 if 会出问题,直接等于 false 拿到一个未初始化完毕的 instance
pennai
2022-04-11 09:50:36 +08:00
如果不加 volatile ,取得锁进行初始化的线程对变量的更新操作不一定能及时地被其他线程感知,其他线程有可能还是会判断 uniqueInstance == null 为 true ,volatile 是保证了可见性,invalidate 了其他线程工作内存的变量副本。

"普通变量与 volatile 变量的区别是,volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此我们可以说 volatile 保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。"
出自深入理解 java 虚拟机第三版
mxalbert1996
2022-04-11 10:03:07 +08:00
不加 volatile 也不会有错但是加了 volatile 可以在其他线程已经生成实例时直接返回,避免同步,所以性能会更好。
liudaolunhuibl
2022-04-11 10:03:09 +08:00
这里加 volatile 不是为了防止指令重排是为了线程之间的可见性
XieQing0428
2022-04-11 10:20:29 +08:00
@Suddoo 啊?能问问原因吗,我平常还挺经常用的
Leviathann
2022-04-11 10:27:29 +08:00
两线程,一个判断完第一个 null ,另一个已经拿完锁创建并返回了
那不加 volatile 第一个线程接下来进到同步块里的那个判空不是就无效了吗,直接从工作区取的,和外层的判空完全一致了
fantastM
2022-04-11 10:33:50 +08:00
@Suddoo #5 这段 double-checked 单例模式是懒加载的,和 enum 还是有点区别的
praxis
2022-04-11 10:35:25 +08:00
uniqueInstance = new Singleton(); 会被编译成三条计算机指令
1 、为 uniqueInstance 分配一个内存地址 A
2 、在内存地址 A 上初始化 uniqueInstance 实例
3 、把内存 A 的地址赋值给 uniqueInstance 变量
如果不禁止指令重排 可能导致 顺序变为 1 、3 、2
这样的话 当一个线程执行到 1 、3 得到的是一个未初始化的 uniqueInstance 而另一个线程执行到第一个 if 就会返回一个未初始化的 uniqueInstance
golangLover
2022-04-11 10:46:12 +08:00
@xuyang2 @Suddoo 为什么这么说
fkdog
2022-04-11 10:56:47 +08:00
两个线程,假如 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
2022-04-11 11:22:15 +08:00
@XieQing0428
有很多 design pattern 是为了给 java 、C++这帮 oop 的语言当年没有函数这个抽象擦屁股
搜 design pattern functional programming
当然 fp 语言有自己的 pattern ,比如 monad
另外随着 java pattern matching 能力的到来,Visitor Pattern Considered Pointless (这个是 oracle java 团队成员的一篇博文)
Suddoo
2022-04-11 12:13:10 +08:00
2022 年了,还是会有傻逼面试官问设计模式,就单例讲出一堆“底层”原理。时代变了,明明有了自动挡汽车,非要开手动挡
Suddoo
2022-04-11 12:15:28 +08:00
@TWorldIsNButThis 是这样的,因为当时 Java 语言还不完善,才弄出这么多补救措施,但现在,很多设计模式已经没有存在的必要了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/846190

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX