jahcy 2010-01-28 11:41
浏览 288
已采纳

单例模式并发的问题!

[code="java"]
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}

return instance;

}
[/code]

在一本并发书上看见的,说这个单例会造成有一些问题,具体原因是因为java对读写共享对象的域时并不保证可线性化性,甚至不保证顺序一致性。原因在于,如果严格遵循顺序一致性,那么将会导致已被广泛采用的编译优化技术变得无效。

大牛们,解释下?说实话,我是看的一头雾水。
书中指出这段代码有误,但是紧跟的内容感觉和这段牛头不对马嘴,紧跟的内容大致讨论了同步块在线程中的作用,以及对线程cache拷贝和共享存储器之间的关系。

书名:多处理编程的艺术 - P42


顺便说两句闲话,发帖到论坛被弄成隐藏贴。我想知道为什么会被隐藏。我感觉这个问题超出了问题的本生,很有高度的一个问题。
当然也有可能很菜的我,不能理解各位大神的思想。

  • 写回答

7条回答 默认 最新

  • q3005218046 2010-01-28 20:09
    关注

    这道题答案不是很重要,思路比较重要,上面有的人说的答案也对,但是不精确(有时候不精确不会产生大的问题,有时候就会,下面会解释

    这个观点),所以LZ最好亲自试试下面的过程。另外,本题涉及几个知识点。我详细解释下。
    1.如果你是想在JAVA代码级别解释这个问题,那么你是在浪费时间。这个问题必须到JVM生成的代码级别讨论(很多问题都是这个样子,在

    JAVA代码级别讨论不仅浪费时间,而且没有意义,记得有人跟我说过一句话:在你所处理的层面,问题根本还没有浮现(非编程问题))。
    2.
    [code="java"]
    public class TestJVM {
    public static void main(String[] args)
    {
    TestJVM abc = new TestJVM();
    }
    }
    [/code]
    代码用javap -c 命令反编译TestJVM.class文件后(我建议你自己试试),生成
    ...
    [code="java"]
    public TestJVM();
    Code:
    0: aload_0
    1: invokespecial #8; //Method java/lang/Object."":()V
    4: return
    public static void main(java.lang.String[]);
    Code:
    0: new #1; //class TestJVM
    3: dup
    4: invokespecial #16; //Method "":()V
    7: astore_1
    8: return
    ...
    [/code]
    解释这段代码是这道问题的第一步,建议你大概查阅下JVM规范,因为我也刚查了。
    (1) new 的含义是创造一块内存,并且在堆栈上压入指向这块内存的引用。
    (2) dup的含义是将栈顶复制,并压入栈。(所以现在有了两个指向刚才分配内存的引用)
    (3) invokespecial意思是将分配的内存中初始化对象。
    (4) astore_1是将栈顶压入本地变量。
    (这段过程,我建议你自己多画几遍,体会下JVM"面向堆栈"的概念,JVM规范第一章最好看看)
    3.上面的四个步骤(绝对的物理过程),其实就是三件事(体会一下原子语句的含义):
    a.给实例分配内存。
    b.初始化构造器
    c.将引用指向分配的内存空间(注意到这步引用就非null了)。
    一般来说,我们期望执行的步骤是a->b->c,然而,由于JVM乱序执行的特性(自己查查这句话在哪,别轻易相信别人,虽然有时候文档也是会

    骗人的-!-),可能执行的顺序是a->c->b。当a->c->b这样执行时候,假如刚执行完c,这样线程2访问这个引用,发现引用不为空,他就对相

    应的内存做操作,这样就会发生错误,这种错误想必不容易发现(那是不是不容易发生?取决于具体的应用环境。)。

    4.问题的关键用一句话来概括,就是这个意思:if(instance==null),如果instance !=null,那么instance就真的准备好了么?
    所以,最原始的写法虽然慢,但是不会产生这种问题,因为原始写法把判断是否等于null的语句,也给锁起来了。只有得到锁,才有资格判断


    5.上面的几条,你也许看了第四条,或者大概明白前几条,你的问题就能解答了。不精确的了解似乎也能回答,但是,有好多误解就产生了。
    比如,有人说,加了valatile类型修饰(JVM1.5以后)符可以将LZ的写法变对,如private volatile static Singleton instance = null;
    其实这是不对的,valatile(LZ想想为什么valatile影响效率?理解下寄存器和内存的效率差别)无非说的就是线程是不能保留共享对象的本地

    拷贝(正常情况线程是可以保留的),那是不是每次去内存中取,就能保证单例对象的正常初始化呢?很明显,这完全是两个问题。

    6.很多细节问题(编程方面),你都得查查英文文档,得自己写试试,中文大家说的话都非常像(因为都是同一本书里面说的,再加上第一个

    人的翻译水平不咋样),很多误解就此产生。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(6条)

报告相同问题?

悬赏问题

  • ¥15 交替优化波束形成和ris反射角使保密速率最大化
  • ¥15 树莓派与pix飞控通信
  • ¥15 自动转发微信群信息到另外一个微信群
  • ¥15 outlook无法配置成功
  • ¥30 这是哪个作者做的宝宝起名网站
  • ¥60 版本过低apk如何修改可以兼容新的安卓系统
  • ¥25 由IPR导致的DRIVER_POWER_STATE_FAILURE蓝屏
  • ¥50 有数据,怎么建立模型求影响全要素生产率的因素
  • ¥50 有数据,怎么用matlab求全要素生产率
  • ¥15 TI的insta-spin例程