Java 对单身者进行双重检查似乎效果很好,但不会被打破,为什么?

Java 对单身者进行双重检查似乎效果很好,但不会被打破,为什么?,java,multithreading,singleton,double-checked-locking,Java,Multithreading,Singleton,Double Checked Locking,我已经读到,singleton的双重检查机制是失败的,因为JVM遵循的某些内存模型使得引用读取不为null,即使构造函数没有完全执行 我在下面的代码中通过在构造函数中进行耗时的操作来测试它,但即使这样,它似乎也可以正常工作 public class Singleton { private static Singleton singleton; private Integer i = 0; private Singleton() { for(long j = 0; j<99999

我已经读到,singleton的双重检查机制是失败的,因为JVM遵循的某些内存模型使得引用读取不为null,即使构造函数没有完全执行

我在下面的代码中通过在构造函数中进行耗时的操作来测试它,但即使这样,它似乎也可以正常工作

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;
}
公共类单例{
私有静态单例单例;
私有整数i=0;
私人单身人士(){

对于(长j=0;j首先,不要使用DSL,不是因为它坏了,而是因为它不必要地复杂

enum Singleton {
    INSTANCE;
}
这不仅简单得多,而且速度更快,因为它不需要getInstance()所做的检查

其次,九年前,DSL在Java5.0的内存模型中得到了修复


最后,即使模型被破坏了,也不能保证它会在特定版本的Java上显示出来。只是不能保证它能在所有版本的Java上工作。Sun版本的Java倾向于修复JLS没有很好涵盖的问题。

首先,不要使用DSL,不是因为它被破坏了,而是因为它是不必要的com折叠的

enum Singleton {
    INSTANCE;
}
这不仅简单得多,而且速度更快,因为它不需要getInstance()所做的检查

其次,九年前,DSL在Java5.0的内存模型中得到了修复


最后,即使模型被破坏了,也不能保证它会在特定版本的Java上显示出来。只是不能保证它能在所有版本的Java上工作。Sun版本的Java倾向于修复JLS没有很好涵盖的问题。

首先,正如Peter Lawrey指出的,没有理由使用双重检查的l一点也不紧张

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

由于两者都不做,您的代码可能会发生数据竞争,但这并不意味着您在测试时会看到这种情况。在访问共享变量时,如果没有适当的同步,会看到未初始化的实例,原因是对堆变量的读写重新排序。但JVM不会对这些实例重新排序定量配给只是为了好玩。当它认为它可以提高性能时,它就会这样做。在您的示例代码中,这是不太可能的


但是不要指望它…

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

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

由于两者都不做,您的代码可能会发生数据竞争,但这并不意味着您在测试时会看到这种情况。在访问共享变量时,如果没有适当的同步,会看到未初始化的实例,原因是对堆变量的读写重新排序。但JVM不会对这些实例重新排序定量配给只是为了好玩。当它认为它可以提高性能时,它就会这样做。在您的示例代码中,这是不太可能的


但不要指望它…

像往常一样,线程竞争很难重现。对于一个坏掉的DCL,它甚至比平常更难,因为它在某种意义上是“几乎正确的”。你需要使用volatile来实现一个正确的DCL。(或者更好,如果你只想要一个单例)。如果你想强制执行一个错误,你将不得不多次运行此命令。可能会有上千万次。我会将DCL的中断视为一个事实,并使用
volatile
@kiheru当前的最佳实践是对单例使用
enum
。引用自:
在J2SE 1.4中使用双重检查锁定的危险之一(和早期版本)问题是,它通常看起来是有效的:不容易区分技术的正确实现和存在细微问题的实现。根据编译器、调度程序的线程交错以及其他并发系统活动的性质,由于双重检查的错误实现而导致的故障CKK可能只是间歇性出现。重现失败可能会很困难。
感谢朋友们的回复。IODH概念非常好。为Singleton使用枚举的问题是,如果我们愿意,我们无法从任何其他类扩展Singleton类,否则就很好了。与往常一样,线程竞争很难重现。对于broken DCL这比平常更难,因为它在某种意义上是“几乎正确的”。您需要使用volatile来实现正确的DCL。(或者更好,如果您只需要一个单例)。如果你想强制执行一个错误,你将不得不多次运行此命令。可能会有上千万次。我会将DCL的中断视为一个事实,并使用
volatile
@kiheru当前的最佳实践是对单例使用
enum
。引用自:
在J2SE 1.4中使用双重检查锁定的危险之一(和早期版本)问题是,它通常看起来是有效的:不容易区分技术的正确实现和存在细微问题的实现。根据编译器、调度程序的线程交错以及其他并发系统活动的性质,由于双重检查的错误实现而导致的故障CKK可能只是间歇性出现。重现失败可能会很困难。
感谢朋友们的回复。IODH概念非常好。为Singleton使用枚举的问题是,如果我们愿意,我们无法从任何其他类扩展Singleton类,否则很好