Java 对单身者进行双重检查似乎效果很好,但不会被打破,为什么?
我已经读到,singleton的双重检查机制是失败的,因为JVM遵循的某些内存模型使得引用读取不为null,即使构造函数没有完全执行 我在下面的代码中通过在构造函数中进行耗时的操作来测试它,但即使这样,它似乎也可以正常工作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
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 holdervolatile
来修复双重检查的锁定代码
由于两者都不做,您的代码可能会发生数据竞争,但这并不意味着您在测试时会看到这种情况。在访问共享变量时,如果没有适当的同步,会看到未初始化的实例,原因是对堆变量的读写重新排序。但JVM不会对这些实例重新排序定量配给只是为了好玩。当它认为它可以提高性能时,它就会这样做。在您的示例代码中,这是不太可能的
但是不要指望它…首先,正如Peter Lawrey所指出的,根本没有理由使用双重检查锁定
另一点是,Java内存模型已使用Java 5进行了更改,因此可以通过使用final
实例字段使实例不可变或通过声明singleton holdervolatile
来修复双重检查的锁定代码
由于两者都不做,您的代码可能会发生数据竞争,但这并不意味着您在测试时会看到这种情况。在访问共享变量时,如果没有适当的同步,会看到未初始化的实例,原因是对堆变量的读写重新排序。但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类,否则很好