首页 > 解决方案 > 用一个扭曲的方式实现单例模式

问题描述

这是一个求职面试问题。

用一个扭曲的方式实现单例模式。首先,不是存储一个实例,而是存储两个实例。在每个偶数调用中 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");
    }
}

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

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

想法?

标签: javadesign-patternssingleton

解决方案


最重要的是,需要声明evenInstanceand变量。参见著名的“Double-Checked Locking is Broken”声明:https ://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.htmloddInstancevolatile

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

最后,Singleton构造函数中的检查被打破,并在第二次调用中抛出异常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();
    } 
}

推荐阅读