Java 使用原子引用的单例

Java 使用原子引用的单例,java,singleton,Java,Singleton,使用AtomicReference实现惰性初始化singleton是否正确?如果没有-可能存在哪些问题 import java.io.ObjectStreamException; import java.io.Serializable; import java.util.concurrent.atomic.AtomicReference; public class Singleton implements Serializable { private static final Sin

使用AtomicReference实现惰性初始化singleton是否正确?如果没有-可能存在哪些问题

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicReference;

public class Singleton implements Serializable {

    private static final Singleton _instance = new Singleton();

    private static AtomicReference<Singleton> instance = new AtomicReference<Singleton>();

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance.compareAndSet(null, _instance)) {
            synchronized (_instance) {
                _instance.init();
                instance.set(_instance);
            }
        }
        return instance.get();
    }

    private void init() {
        // do initialization
    }

    private Object readResolve() throws ObjectStreamException {
        return getInstance();
    }

}
import java.io.ObjectStreamException;
导入java.io.Serializable;
导入java.util.concurrent.AtomicReference;
公共类单例实现了可序列化{
私有静态最终单例_实例=新单例();
私有静态AtomicReference实例=新的AtomicReference();
私人单身人士(){
}
公共静态单例getInstance(){
if(instance.compareAndSet(null,_instance)){
已同步(\u实例){
_init();
instance.set(_instance);
}
}
返回instance.get();
}
私有void init(){
//初始化
}
私有对象readResolve()引发ObjectStreamException{
返回getInstance();
}
}
您可以阻止序列化场景,从而强制使其成为单例

所有构造函数都应该是私有的。

不,这很糟糕:

public static Singleton getInstance() {
    // new "singleton" for every method call
    Singleton s = new Singleton();
                   ^^^^^^^^^^^^^^
    if (instance.compareAndSet(null, s)) {
        synchronized (s) {
            s.init();
        }
    }
    return instance.get();
}
使用AtomicReference是一个好主意,但它不会起作用,因为Java没有惰性计算


经典的Post1.5单例方法有:

热切的单身汉:

public final class Singleton{
    private Singleton(){}
    private static final Singleton INSTANCE = new Singleton();
    public Singleton getInstance(){return INSTANCE;}
}
public final class Singleton{
    private Singleton(){}
    private static class Holder{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){return Holder.INSTANCE;}
}
public enum Singleton{
    INSTANCE;
}
具有内部holder类的惰性单例:

public final class Singleton{
    private Singleton(){}
    private static final Singleton INSTANCE = new Singleton();
    public Singleton getInstance(){return INSTANCE;}
}
public final class Singleton{
    private Singleton(){}
    private static class Holder{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){return Holder.INSTANCE;}
}
public enum Singleton{
    INSTANCE;
}
枚举单例:

public final class Singleton{
    private Singleton(){}
    private static final Singleton INSTANCE = new Singleton();
    public Singleton getInstance(){return INSTANCE;}
}
public final class Singleton{
    private Singleton(){}
    private static class Holder{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){return Holder.INSTANCE;}
}
public enum Singleton{
    INSTANCE;
}

您可能应该坚持使用其中的一个

我不明白为什么需要对单例使用
AtomicReference
AtomicReference
允许您以原子方式更改对对象的引用——对于单例,应该只有一个实例,任何人都不能再更改它用于执行您的应用程序。
此外,您的代码没有同步,因此并发请求最终将创建多个
Singleton
类的实例(例如@Sean Patrick Floyd指出)。

您有一个竞争条件,在调用
init
之前,您可以返回一个
Singleton
的实例。如果您想要一个只使用一次的
init
,那么可以包装singleton。然而,我们知道如何以简单、高效的方式实现单例,而可变单例是纯粹的邪恶

更新的代码使用
readResolve
添加反序列化

这里有两个明显的问题

  • 在调用readResolve之前,反向引用可以读取原始对象

  • 即使该类只有一个私有构造函数,它也不是
    final
    <代码>最终版非常重要。手工制作(或使用jig
    Singleton
    implementation创建)的八位字节序列可以反序列化为子类。(反序列化机制调用派生最多的不可序列化基类(必须可供最基本的可序列化类访问)的无参数构造函数。)子类不调用不可访问的
    readResolve


好的,那么可能是让Singleton只实例化一次,然后在成功初始化时进行赋值?“只实例化一次”正是这里的问题所在。如果您使用我列出的方法之一,“仅一次”是有保证的。否则:您可以接近于保证“仅一次”,但您不能获得100%,而且编码是一种痛苦
公共单例getInstance(){return INSTANCE;}
不是线程安全的。此外,如果他们序列化它以获得另一个,也可以对其进行黑客攻击object@Jigar:是的,它是线程安全的,在执行方法之前初始化静态字段,但是关于序列化攻击,您是对的,请使用“枚举单例”。它是最健壮的。IIRC对序列化攻击免疫我想我已经解决了。你能看一看这篇文章吗?可能单独的
init
的想法是你只运行该部分一次。你也可以把它放在CaS之后,让init检查它是否已经被调用,但我真的找不到一个好的线程安全方法来做这件事(CaS成功后,另一个线程可以得到未初始化的单例)nvm找到了oneUm,我确信这仍然是一场代码竞赛。这真的很难看,而且完全没有必要。我明白了:用getInstance替换最后一个get,将不会返回未初始化的单例,尽管这确实是一个很大的难题,使用类加载语义就足以解决这个问题。在答案发布后更改问题中的代码可能不太好。@Sean Patrick Floyd,但新代码更清楚地说明了它仍然存在的问题。尽管它确实在混合中添加了反序列化。