菜菜问一个单例模式加锁的问题

2016-09-23 17:44:52 +08:00
 lawlietxxl

来源: http://www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.html

其中有一个方法

public static SingletonThree getInstance() {
        if (instance == null) { 
            synchronized (SingletonThree.class) {           // 1
                SingletonThree temp = instance;             // 2
                if (temp == null) {
                    synchronized (SingletonThree.class) {   // 3  这里问什么再加一个锁呢?
                        temp = new SingletonThree();        // 4
                    }
                    instance = temp;                        // 5
                }
            }
        }
        return instance;
    }

请问在 //3 的位置,为什么再加一个锁呢?百思不得其解,求教

4020 次点击
所在节点    Java
29 条回复
suikator
2016-09-23 18:10:04 +08:00
可能是为了防止指令重排吧,第 4 和第 5 句,说错了请轻喷, 233
suikator
2016-09-23 18:10:24 +08:00
//4 //5 手滑
ldbC5uTBj11yaeh5
2016-09-23 18:15:47 +08:00
limhiaoing
2016-09-23 18:16:18 +08:00
可能多个线程检查到等于 null ,但只允许 new 一次,好像叫 double-check 什么的。
hactrox
2016-09-23 18:17:31 +08:00
单例模式中这个概念叫“双重锁定”
解释起来要打很多字 =.= 所以顺手给 lz 搜了个资料:
http://blog.csdn.net/ouyang_peng/article/details/8885840
limhiaoing
2016-09-23 18:17:39 +08:00
Java 的 memory order 应该是顺序一致性,所以这里不会重排。
ldbC5uTBj11yaeh5
2016-09-23 18:25:01 +08:00
@limhiaoing 是的,只有 c++ 编译器有这个问题(内存栅栏)。

不过如果能用 std::call_once 的话,一行就解决问题了
http://www.nuonsoft.com/blog/2012/10/21/implementing-a-thread-safe-singleton-with-c11/comment-page-1/
suikator
2016-09-23 18:43:33 +08:00
@hactrox 666 ,很有水平的文章
suikator
2016-09-23 18:54:58 +08:00
@limhiaoing 是我说错了, 5l 说得对, 233
haozhang
2016-09-23 18:59:38 +08:00
我觉得直接用 offical 的单例注解不就完了....
lawlietxxl
2016-09-23 20:03:41 +08:00
@suikator
@limhiaoing
@jigloo

首先感谢各位大大,让我又看到了一些新东西。
我不明白的地方还是没明白,就是为什么要有第二个锁呢?如果没有第二个锁,是否还会出现乱序写入的问题?当线程 1 没有退出最外层的 synchronized , instance == null 是 true ,线程 2 不是也进不去吗?那第二个锁意义在哪里呀 = =
anthow
2016-09-23 20:08:21 +08:00
double check 是指 2 次判断是否为 null 吧。。。 2 次锁不知道为什么。 个人感觉没必要~
貌似 1.5 以上内存模型已经改了吧。
suikator
2016-09-23 20:26:29 +08:00
@lawlietxxl
就是为什么要有第二个锁呢 -> 避免无序写入问题
如果没有第二个锁,是否还会出现乱序写入的问题 -> 会
有第二个锁,是否还会出现乱序写入的问题 -> 会
用 volatile 声明是否还会出现乱序写入的问题 -> 会
当线程 1 没有退出最外层的 synchronized , instance == null 是 true ,线程 2 不是也进不去吗 -> 当线程 1 没有退出最外层的 synchronized , instance 可能不会为 null ,但此时对象初始化不完整,线程 2 中 instance == null 为 false 直接返回初始化不完整的对象,当使用这个对象时,发生未知问题。所以为了避免发生这个问题再加一套锁,但是并不能彻底解决问题。
正确的方式是啥 -> 用 double check 实现单例简直就是个错误示范,尽量用内部类或者枚举实现单例。
anexplore
2016-09-23 20:28:57 +08:00
所谓 double check 是指 2 次检查 instance == null
一般写法
volatile A instance = null;
.....
static A instance() {
if (instance == null) {
synchronized(A.class) {
if (instance == null) {
instance = new A();
}
}
}
}
一般用 static 内部类来实现 Singleton
zonghua
2016-09-23 20:52:55 +08:00
private volatile static SingleDog SingleDog = null;

这种用 volatile 的我也不太懂
caixiexin
2016-09-23 20:58:17 +08:00
@anexplore 更好的方式是用 enum 来实现单例。
lawlietxxl
2016-09-23 21:38:41 +08:00
@suikator
首先感谢你的时间和回复!!的确用内部类是一个很好的实现单例的方式。( enum 的我还不知道,我查查)

然后。。。我还是不明白。。。如下图,我把第二个锁去掉了,我是在 instance=temp 进行的赋值,那么会有乱序写入吗?如果没有乱序写入的话,那么 instance 也就不会被提前指向一个半成品了呀。所以第二个锁岂不是没有意义。。求教

public static SingletonThree getInstance() {
if (instance == null) {
synchronized (SingletonThree.class) { // 1
SingletonThree temp = instance; // 2
if (temp == null) {
temp = new SingletonThree(); // 4
instance = temp; // 5
}
}
}
return instance;
}
nifury
2016-09-23 21:51:08 +08:00
@lawlietxxl 嗯……你这么写我一时想不出是否有问题
然而单例的话为何不让 classloader 做呢?
直接 static SingletonThree s = new SingletonThree ();
suikator
2016-09-23 22:12:25 +08:00
@lawlietxxl //4 //5 可能被 jit 直接 inline ,变成 instance = new SingletonThree() 这样就又出现了乱序写入问题。哈哈,这逼我装不下去了,已经超出我知识范围了。
lawlietxxl
2016-09-23 23:01:02 +08:00
@suikator 似乎是这么一回事!厉害了 我的哥

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

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

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

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

© 2021 V2EX