1

我已经读过单例的双重检查机制是失败的,因为 JVM 后面的一些内存模型使得即使构造函数没有完全执行,引用读取也不为空。

我尝试通过在下面的代码中的构造函数中进行操作来测试相同的内容,但即便如此它似乎工作正常。

public class Singleton {
private static Singleton singleton;
private Integer i =  0;
private Singleton() {

    for(long j = 0; j<99999999; j++){
        double k = Math.random();
        k= k+1;
    }
    i = 10;
}

private static Singleton getSinglton() {
    if(singleton == null){
        synchronized (Singleton.class) {
            if(singleton == null){
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}

public static void main(String[] args) {
    Runnable runnable1 = new Runnable() {

        @Override
        public void run() {
            Singleton singleton = Singleton.getSinglton();
            System.out.println(singleton.getI());
        }
    };
    Thread t1 = new Thread(runnable1);
    Thread t2 = new Thread(runnable1);
    Thread t3 = new Thread(runnable1);
    Thread t4 = new Thread(runnable1);
    Thread t5 = new Thread(runnable1);
    Thread t6 = new Thread(runnable1);
    t1.start();
    t2.start();
    t3.start();
    t4.start();
    t5.start();
    t6.start();
}

public void setI(Integer i) {
    this.i = i;
}

public Integer getI() {
    return i;
}

}

我得到的结果是 10 10 10 10 10 10

我期望很少有线程读取值 0 而不是 10,但每次该值被正确读取为 10 ,所以它在 J​​ava SE-1.6 中解决了问题,因为我使用的是相同的?

4

2 回答 2

1

首先,不要使用DSL,不是因为它坏了,而是因为它不必要地复杂。

enum Singleton {
    INSTANCE;
}

这不仅简单得多,而且速度更快,因为它不需要您的 getInstance() 所做的检查。

其次,DSL 在九年前的 Java 5.0 中被固定在内存模型中。

最后,即使模型被破坏,也不能保证它会针对特定版本的 Java 显示出来。只是不能保证它适用于所有版本的 Java。Java 的 Sun 版本倾向于修复 JLS 没有很好地解决的问题。

于 2013-10-06T13:02:00.003 回答
0

首先,正如 Peter Lawrey 所指出的,根本没有理由使用双重检查锁定。

另一点是,Java 内存模型已随 Java 5 更改,因此可以通过使用final实例字段使实例不可变或通过声明单例持有者来修复双重检查锁定代码volatile

由于您都没有这样做,因此您的代码可能会受到数据竞争的影响,但这并不意味着您在测试时会看到这一点。在没有正确同步的情况下访问共享变量时看到未初始化实例的原因是对堆变量的读取和写入重新排序。但是 JVM 不会仅仅为了好玩而重新排序这些操作。当它认为它可以提高性能时,它就会这样做。在您的示例代码中,这不太可能。

但不要指望它......</p>

于 2013-10-08T08:56:11.593 回答