扭转实现单例模式

Abh*_*kar 2 java singleton design-patterns

这是工作面试的问题。

实施单例模式。首先,不是存储一个实例,而是存储两个实例。在的每个偶数调用中 getInstance(),返回第一个实例,在的每个奇数调用中 getInstance(),返回第二个实例。

我的实现如下:

public final class Singleton implements Cloneable, Serializable {
    private static final long serialVersionUID = 42L;
    private static Singleton evenInstance;
    private static Singleton oddInstance;
    private static AtomicInteger counter = new AtomicInteger(1);

    private Singleton() {
        // Safeguard against reflection
        if (evenInstance != null || oddInstance != null) {
            throw new RuntimeException("Use getInstance() instead");
        }
    }

    public static Singleton getInstance() {
        boolean even = counter.getAndIncrement() % 2 == 0;
        // Make thread safe
        if (even && evenInstance == null) {
            synchronized (Singleton.class) {
                if (evenInstance == null) {
                    evenInstance = new Singleton();
                }
            }
        } else if (!even && oddInstance == null) {
            synchronized (Singleton.class) {
                if (oddInstance == null) {
                    oddInstance = new Singleton();
                }
            }
        }

        return even ? evenInstance : oddInstance;
    }

    // Make singleton from deserializaion
    protected Singleton readResolve() {
        return getInstance();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Use getInstance() instead");
    }
}
Run Code Online (Sandbox Code Playgroud)

看到问题了吗?可能会进入第一个调用getInstance,并且线程被抢占。然后,可以进入第二个呼叫,getInstance但会得到oddInstance而不是evenInstance

显然,可以通过使其getInstance同步来防止此情况,但这是不必要的。在单例的生命周期中,只需要两次同步,而不是每个getInstance调用都需要同步。

有想法吗?

Mat*_*ans 6

最重要的是,必须声明evenInstanceoddInstance变量volatile。请参阅著名的“双重检查锁定已损坏”声明:https : //www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

此外,对于偶数和奇数实例,您实际上应该在同步块中使用不同的对象,以便可以同时构造它们。

最终,Singleton构造函数中的check 被破坏,并且在第二次调用中将引发异常getInstance()

除此之外,还可以,但是如果您自己不进行并发工作,那就更好了:

public final class Singleton implements Cloneable, Serializable {
    private static AtomicInteger counter = new AtomicInteger(1);


    public static Singleton getInstance() {
        if (counter.getAndIncrement() % 2 == 0) {
            return EvenHelper.instance;
        } else {
            return OddHelper.instance;
        }
    }

    private static class EvenHelper {
        //not initialized until the class is used in getInstance()
        static Singleton instance = new Singleton();
    }

    private static class OddHelper {
        //not initialized until the class is used in getInstance()
        static Singleton instance = new Singleton();
    } 
}
Run Code Online (Sandbox Code Playgroud)

  • @Abhijit,“输入方法”实际上是一个多步骤的过程,而不是原子操作,并且除诸如getAndIncrement中的存储障碍外,其内存效果都不必然在其他内核上按wrt操作排序。此外,如果要“同步” getInstance,则只能在与“ getAndIncrement”现在完全相同的位置添加带有内存屏障的锁定操作。 (2认同)