Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/390.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 使用twist实现singleton模式_Java_Design Patterns_Singleton - Fatal编程技术网

Java 使用twist实现singleton模式

Java 使用twist实现singleton模式,java,design-patterns,singleton,Java,Design Patterns,Singleton,这是一个面试问题 通过扭曲实现单例模式。首先,而不是 存储一个实例,存储两个实例。在每一个偶数的电话里 getInstance(),在对 getInstance(),返回第二个实例 我的执行情况如下: public final class Singleton implements Cloneable, Serializable { private static final long serialVersionUID = 42L; private static Singleton

这是一个面试问题

通过扭曲实现单例模式。首先,而不是 存储一个实例,存储两个实例。在每一个偶数的电话里
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
,而不是
evencInstance

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


想法?

你不是说单例必须惰性地初始化,所以我假设不是

你可能想得太多了。试试这个:

public final class Singleton implements Cloneable, Serializable {
    private static Singleton[] instances = new Singleton[]{new Singleton(), new Singleton()};
    private static AtomicInteger counter = new AtomicInteger();

    private Singleton() {} // further protection not necessary

    public static Singleton getInstance() {
        return instances[counter.getAndIncrement() % 2];
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Use getInstance() instead");
    }
}
如果您担心反射攻击,只需使用enum,它是防弹的,类似于:

public final class Singleton implements Cloneable, Serializable {
    private static AtomicInteger counter = new AtomicInteger();
    private enum SingletonInstance implements Cloneable, Serializable {
        ODD, EVEN;
        private Singleton instance = new Singleton();
    }

    private Singleton() {} // further protection not necessary

    public static Singleton getInstance() {
        return SingletonInstance.values()[counter.getAndIncrement() % 2].instance;
    }

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

最重要的是,需要声明
evenInstance
oddInstance
变量
volatile
。请参阅著名的“双重检查锁定已断开”声明:

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

最后,
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();
    } 
}
你看到问题了吗?第一次调用可能进入
getInstance
,线程被抢占。然后,第二个调用可能会进入getInstance,但将获得oddInstance而不是evenInstance

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

如果您真的想“修复”这个“问题”,您唯一的选择就是同步
getInstance
。但人们如何看待这个问题呢?如果第一个线程正好在
getInstance
之后被抢占呢

在多线程中,事件的绝对顺序不是完全确定的。所以你总是有这样的风险,行动似乎是不正常的


顺便说一句:对“反射攻击”的攻击有一个严重的缺陷!它阻止构建
甚至实例
!我想你应该把
|
改成
&&
。但这仍然不能给您任何保证,因为“反射攻击”可能在第一次调用和第二次调用之间。您必须在类加载时预构造这两个实例,以确保99%的安全性


如果您担心它,那么您肯定不应该实现
Cloneable
Serializable

这并不能解决我描述的比赛条件。您仍然可以抢占线程1,而线程2出现并获得错误的实例。@AbhijitSarkar不,这段代码没有竞争条件,因为这两个实例都是在允许调用
getInstance()
方法之前创建的(由类装入器在装入类时创建)。这不是我要说的,我的代码在没有更改的情况下可以很好地处理创建。要求是第一个调用获取奇数调用的实例,第二个调用获取偶数实例。我的代码中的错误,也存在于你的代码中,是第二次调用可能会得到奇数实例。在我看来,
enum
方法非常聪明,但不可行。它总是看起来像一个黑客,不是很方便。如果你给我你的库,并且这个库声明了一个
enum singletonistance
,我希望它是一个
enum
。可能这个
enum
用于控制返回的方法实例。我绝对不希望它背后有像singleton(例如database connector)这样的复杂类。这意味着这种方法附加了一个约定,可能是一个命名约定。这要求您的库的使用者了解此约定。@BionicCode我同意您的看法,enum单例模式感觉像是一种黑客行为,因为它不是一个实际的enum,但a)OP似乎担心如何防御反射黑客行为(火中带火),B)ppl不断将此模式推到我们的脖子上:(这是如何解决我在问题和对@Bohemian答案的评论中描述的问题的?这个问题实际上并不存在。如果两个调用不在同一线程中,那么“第一个”调用是先执行
getAndIncrement
的调用,该顺序决定了哪一个获得偶数实例。没有其他“first”的定义可以应用。“first”的定义也不适用当然还有另一个定义。首先是首先进入
getInstance
方法的线程。但是,由于解释取决于您询问的对象,我认为您所说的可以被视为可行的替代方法。我想补充一点,当使用此模式时,JVM的内存管理可以保证真正的线程安全,正如内部类“
evenheloper.instance
的多次(并发)调用被阻止(或同步),直到类被创建。此外,这种方法隐式延迟加载实际的单例实例。另一个副作用是,这种方法消除了不安全的if语句(双重检查模式)。@Abhijit,“进入方法”实际上是一个多步骤的过程,而不是一个原子操作,它的任何内存效果都不一定是在其他核上按顺序进行的w.r.t.操作,除了内存限制