第一个关闭:是的,我知道在Java中使用单例的最佳方法是使用enum
s,但如果由于某种原因需要子类化单例类,则不能使用枚举,所以......
public static Singleton getInstance()
{
if (singleton == null)
{
synchronized(Singleton.class)
{
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
Geary说这是'双重检查锁定'优化
无法保证正常工作,因为编译器可以自由分配 在单例之前的值为单例成员变量 构造函数被调用。如果发生这种情况,可以抢占线程1 在分配了单例引用之后,但在之前 singleton已初始化,因此Thread 2可以返回对a的引用 未初始化的单身实例。
我的问题:以下更改是否可以解决该问题?我已经开始阅读Goetz的Java并发书了,似乎允许编译器在线程内操作进行混乱,所以我不太自信......在我看来,singleton = temp;
是一个原子操作,在这种情况下,我认为它应该。请解释一下。
public static Singleton getInstance()
{
if (singleton == null)
{
synchronized(Singleton.class)
{
if(singleton == null) {
Singleton temp = new Singleton();
singleton = temp;
}
}
}
return singleton;
}
答案 0 :(得分:2)
第二个代码与第一个代码顺序一致(它们在单个线程环境中严格相同),并且不会引入任何额外的内存同步点。
所以是的,编译器有权重写第二个代码并将其转换为第一个代码,这意味着它也是不安全的。
singleton = temp;
是原子的事实在这里没有用。它只表示singleton为null或与temp保持相同的引用。但这并不妨碍temp / singleton指向“非构造”对象。
Java内存模型在发生前(HB)关系方面起作用。在两个代码中只有一个hb:同步块的退出hb后续进入该块。 if (singleton == null)
与singleton=…
不共享任何关系,因此可以进行重新排序。
最重要的是,修复它的唯一方法是在两个语句之间引入一个hb:在同步块中移动if或者例如标记singleton volatile。
答案 1 :(得分:1)
答案取决于可以通过编译器应用于第二代码的优化(这意味着可以通过编译器将第二代码转换为第一代码)。您可以使用AtomicReference编写代码,以避免此问题:
private static AtomicReference<Singleton> singleton = new AtomicReference<Singleton>(null);
...
public static Singleton getInstance()
{
if (singleton.get() == null)
{
synchronized(Singleton.class)
{
if(singleton.get() == null) {
singleton.compareAndSet(null, new Singleton());
}
}
}
return singleton.get();
}
答案 2 :(得分:0)
删除错误,为讨论保留空白答案。哇,生活和学习!