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 ?
1
hay313955795 2022-04-11 09:35:55 +08:00
加了 synchronized 不代表一定只有一个线程进到了方法里面..之前有个视频里有讲过..
链接: https://pan.baidu.com/s/1bb-6itr18pnILc6yZCdvyQ 提取码: fbpy 复制这段内容后打开百度网盘手机 App ,操作更方便哦 |
2
jwh199588 2022-04-11 09:38:10 +08:00
防止重排序的问题,uniqueInstance = new Singleton();这里存在一个排序的问题
|
3
Kaiv2 2022-04-11 09:40:07 +08:00
|
4
xuyang2 2022-04-11 09:40:12 +08:00
都什么年代了,还用 `static Singleton instance` ...
|
5
Suddoo 2022-04-11 09:41:23 +08:00 via iPhone
用 enum 吧,单例模式已经没意义了,甚至以前的那些设计模式都没有意义了,Java 语言是在不断演进的
|
6
wolfie 2022-04-11 09:42:55 +08:00
其他线程即时可见
|
7
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... } |
8
MapHacker 2022-04-11 09:49:13 +08:00
加锁之前的那个 if 会出问题,直接等于 false 拿到一个未初始化完毕的 instance
|
9
pennai 2022-04-11 09:50:36 +08:00
如果不加 volatile ,取得锁进行初始化的线程对变量的更新操作不一定能及时地被其他线程感知,其他线程有可能还是会判断 uniqueInstance == null 为 true ,volatile 是保证了可见性,invalidate 了其他线程工作内存的变量副本。
"普通变量与 volatile 变量的区别是,volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此我们可以说 volatile 保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。" 出自深入理解 java 虚拟机第三版 |
10
mxalbert1996 2022-04-11 10:03:07 +08:00 via Android
不加 volatile 也不会有错但是加了 volatile 可以在其他线程已经生成实例时直接返回,避免同步,所以性能会更好。
|
11
liudaolunhuibl 2022-04-11 10:03:09 +08:00
这里加 volatile 不是为了防止指令重排是为了线程之间的可见性
|
12
XieQing0428 2022-04-11 10:20:29 +08:00
@Suddoo 啊?能问问原因吗,我平常还挺经常用的
|
13
Leviathann 2022-04-11 10:27:29 +08:00
两线程,一个判断完第一个 null ,另一个已经拿完锁创建并返回了
那不加 volatile 第一个线程接下来进到同步块里的那个判空不是就无效了吗,直接从工作区取的,和外层的判空完全一致了 |
15
praxis 2022-04-11 10:35:25 +08:00 1
uniqueInstance = new Singleton(); 会被编译成三条计算机指令
1 、为 uniqueInstance 分配一个内存地址 A 2 、在内存地址 A 上初始化 uniqueInstance 实例 3 、把内存 A 的地址赋值给 uniqueInstance 变量 如果不禁止指令重排 可能导致 顺序变为 1 、3 、2 这样的话 当一个线程执行到 1 、3 得到的是一个未初始化的 uniqueInstance 而另一个线程执行到第一个 if 就会返回一个未初始化的 uniqueInstance |
16
golangLover 2022-04-11 10:46:12 +08:00 via Android
|
17
fkdog 2022-04-11 10:56:47 +08:00 1
两个线程,假如 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 是为了防止代码进入不必要的同步块,提高性能。 |
18
TWorldIsNButThis 2022-04-11 11:22:15 +08:00 1
@XieQing0428
有很多 design pattern 是为了给 java 、C++这帮 oop 的语言当年没有函数这个抽象擦屁股 搜 design pattern functional programming 当然 fp 语言有自己的 pattern ,比如 monad 另外随着 java pattern matching 能力的到来,Visitor Pattern Considered Pointless (这个是 oracle java 团队成员的一篇博文) |
19
Suddoo 2022-04-11 12:13:10 +08:00 via iPhone
2022 年了,还是会有傻逼面试官问设计模式,就单例讲出一堆“底层”原理。时代变了,明明有了自动挡汽车,非要开手动挡
|
20
Suddoo 2022-04-11 12:15:28 +08:00 via iPhone
@TWorldIsNButThis 是这样的,因为当时 Java 语言还不完善,才弄出这么多补救措施,但现在,很多设计模式已经没有存在的必要了
|
22
git00ll 2022-04-11 13:51:56 +08:00
a 线程使用赋值语句赋值后,b 线程可能看不到
|
23
crackhopper 2022-04-11 13:57:28 +08:00
每次看到 volatile 就头疼。C++和 Java 的 volatile 还不一样。
|
24
liudaolunhuibl 2022-04-11 13:58:51 +08:00
@BQsummer 用枚举吧,话说这种手写单例模式的还怎么见过,一般你需要某个类单例就把它交给他 spring 就好了
|
25
chengyiqun 2022-04-11 14:10:37 +08:00
推荐用静态内部类的写法代替这种写法, 或者用枚举, 最好用 spring 直接管理.
|
26
ligiggy 2022-04-11 14:24:05 +08:00
@crackhopper 哈哈哈,太经典了
|
27
quicksand 2022-04-11 14:39:50 +08:00
|
28
zhady009 2022-04-11 14:58:19 +08:00
要延迟加载用内部静态类的方式就好了 这种写法不知道多少年前的
如果不用延迟加载用 static final 或者直接 enum |
29
MakHoCheung 2022-04-11 15:28:10 +08:00
同意 25 楼,与其深究这种容易出问题需要了解底层的方式,还不如去掌握没问题的方式
|
30
Red998 2022-04-15 16:45:59 +08:00
是对象初始化有几个步骤、申请内存--初始化--赋值--返回内存引用地址 。多线程情况下可能会存在 获取半初始化对象 。所有才需要 volatile 防止指令冲排序 至于为什么 cpu 指令会这样。个人觉得效率高吧。无论怎么样执行最后都是返回一个对象引用地址。 不过话又说回来 我真没看到现在有这么用 。一般的单例用的多也没啥大问题。非要完美一点 我觉得那个枚举方式就很好
|
31
goalidea 2022-12-03 18:39:08 +08:00
没有必要搞这么细,这也就面试中可能会问一问,工作中都是懒汉,不然就内部类实现
|