我已经读过,单例的双重检查机制是失败的,因为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时,所以Java SE-1.6中的问题解决了,因为我使用的是相同的?
答案 0 :(得分:1)
首先,不要使用DSL,不要因为它被破坏,而是因为它不必要地复杂化。
enum Singleton {
INSTANCE;
}
这不仅更简单,而且更快,因为它不需要你的getInstance()检查。
其次,九年前在Java 5.0的内存模型中修复了DSL。
最后,即使模型被破坏,也无法保证它会显示特定版本的Java。只是不能保证它不适用于所有版本的Java。 Sun版本的Java倾向于修复JLS未能很好地解决的问题。
答案 1 :(得分:0)
首先,正如Peter Lawrey指出的那样,没有理由使用双重检查锁定。
另一点是,Java内存模型已经使用Java 5进行了更改,因此可以通过使实例使用final
实例字段不可变或通过声明单例来修复双重检查的锁定代码持有人volatile
。
由于您没有这样做,您的代码会受到可能的数据争用的影响,但这并不意味着您在测试时会看到它。在没有正确同步的情况下访问共享变量时看到未初始化实例的原因是对堆变量的读取和写入的重新排序。但是JVM并没有为了好玩而重新排序这些操作。当它认为它可以提高性能时就会这样做。在您的示例代码中,这是不太可能的。
但不要依赖它......