5

单例模式 双检测问题请教

 2 years ago
source link: https://www.v2ex.com/t/846190
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

V2EX  ›  Java

单例模式 双检测问题请教

  followyourheart · 3 小时 7 分钟前 · 899 次点击
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 ?

hay313955795

hay313955795      3 小时 0 分钟前

加了 synchronized 不代表一定只有一个线程进到了方法里面..之前有个视频里有讲过..

链接: https://pan.baidu.com/s/1bb-6itr18pnILc6yZCdvyQ 提取码: fbpy 复制这段内容后打开百度网盘手机 App ,操作更方便哦

jwh199588

jwh199588      2 小时 57 分钟前

防止重排序的问题,uniqueInstance = new Singleton();这里存在一个排序的问题

xuyang2

xuyang2      2 小时 55 分钟前

都什么年代了,还用 `static Singleton instance` ...

Suddoo

Suddoo      2 小时 54 分钟前 via iPhone

用 enum 吧,单例模式已经没意义了,甚至以前的那些设计模式都没有意义了,Java 语言是在不断演进的

wolfie

wolfie      2 小时 53 分钟前

其他线程即时可见

qgs

qgs      2 小时 50 分钟前

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      2 小时 46 分钟前

加锁之前的那个 if 会出问题,直接等于 false 拿到一个未初始化完毕的 instance

pennai

pennai      2 小时 45 分钟前

如果不加 volatile ,取得锁进行初始化的线程对变量的更新操作不一定能及时地被其他线程感知,其他线程有可能还是会判断 uniqueInstance == null 为 true ,volatile 是保证了可见性,invalidate 了其他线程工作内存的变量副本。

"普通变量与 volatile 变量的区别是,volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此我们可以说 volatile 保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。"
出自深入理解 java 虚拟机第三版

mxalbert1996

mxalbert1996      2 小时 33 分钟前 via Android

不加 volatile 也不会有错但是加了 volatile 可以在其他线程已经生成实例时直接返回,避免同步,所以性能会更好。

liudaolunhuibl

liudaolunhuibl      2 小时 33 分钟前

这里加 volatile 不是为了防止指令重排是为了线程之间的可见性

XieQing0428

XieQing0428      2 小时 15 分钟前

@Suddoo 啊?能问问原因吗,我平常还挺经常用的

Leviathann

Leviathann      2 小时 8 分钟前

两线程,一个判断完第一个 null ,另一个已经拿完锁创建并返回了
那不加 volatile 第一个线程接下来进到同步块里的那个判空不是就无效了吗,直接从工作区取的,和外层的判空完全一致了

fantastM

fantastM      2 小时 2 分钟前

@Suddoo #5 这段 double-checked 单例模式是懒加载的,和 enum 还是有点区别的

a794443642

a794443642      2 小时 0 分钟前   ❤️ 1

uniqueInstance = new Singleton(); 会被编译成三条计算机指令
1 、为 uniqueInstance 分配一个内存地址 A
2 、在内存地址 A 上初始化 uniqueInstance 实例
3 、把内存 A 的地址赋值给 uniqueInstance 变量
如果不禁止指令重排 可能导致 顺序变为 1 、3 、2
这样的话 当一个线程执行到 1 、3 得到的是一个未初始化的 uniqueInstance 而另一个线程执行到第一个 if 就会返回一个未初始化的 uniqueInstance

golangLover

golangLover      1 小时 49 分钟前 via Android

fkdog

fkdog      1 小时 39 分钟前

两个线程,假如 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      1 小时 13 分钟前

@XieQing0428
有很多 design pattern 是为了给 java 、C++这帮 oop 的语言当年没有函数这个抽象擦屁股
搜 design pattern functional programming
当然 fp 语言有自己的 pattern ,比如 monad
另外随着 java pattern matching 能力的到来,Visitor Pattern Considered Pointless (这个是 oracle java 团队成员的一篇博文)

Suddoo

Suddoo      23 分钟前 via iPhone

2022 年了,还是会有傻逼面试官问设计模式,就单例讲出一堆“底层”原理。时代变了,明明有了自动挡汽车,非要开手动挡

Suddoo

Suddoo      20 分钟前 via iPhone

@TWorldIsNButThis 是这样的,因为当时 Java 语言还不完善,才弄出这么多补救措施,但现在,很多设计模式已经没有存在的必要了

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK