Реализация шаблона синглтона с поворотом - PullRequest
2 голосов
/ 01 июля 2019

Это вопрос собеседования.

Реализуйте шаблон синглтона с поворотом.Во-первых, вместо хранения одного экземпляра, сохраните два экземпляра.И в каждом четном вызове 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, но вместо evenInstance будет получен oddInstance.

Очевидно, что это можно предотвратить, если синхронизировать getInstance, но это не нужно.Синхронизация требуется только дважды в жизненном цикле одиночного вызова, а не для каждого отдельного вызова getInstance.

Идеи?

Ответы [ 3 ]

5 голосов
/ 01 июля 2019

Самое главное, переменные evenInstance и oddInstance должны быть объявлены volatile.См. Известное объявление «Двойная проверка блокировки сломана»: https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Кроме того, вы должны действительно использовать разные объекты в блоках синхронизации для четных и нечетных экземпляров, чтобы они могли быть созданы одновременно.

Наконец, проверка в конструкторе 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();
    } 
}
3 голосов
/ 01 июля 2019

Вы не говорите, что синглтон должен быть лениво инициализирован, поэтому я предполагаю, что нет ...

Вы могли бы переоценить это. Попробуйте это:

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");
    }
}

Если вы беспокоитесь об атаках отражения, просто используйте перечисление, которое является пуленепробиваемым, что-то вроде:

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();
    }
}
0 голосов
/ 01 июля 2019

Вы видите проблему?Первый вызов может ввести getInstance, и поток будет прерван.Затем второй вызов может войти в getInstance, но вместо evenInstance он получит oddInstance.

Очевидно, этого можно избежать, если синхронизировать getInstance, но это не нужно.Синхронизация требуется только дважды в жизненном цикле одиночного вызова, а не для каждого отдельного вызова getInstance.

Если вы действительно хотите «исправить» эту «проблему», единственным вариантом является синхронизацияgetInstance.Но как можно увидеть эту проблему?Что если первый поток прерывается сразу после getInstance?

В многопоточности абсолютный порядок событий не является полностью детерминированным.Таким образом, у вас всегда есть риск того, что действия кажутся не в порядке.


Кстати: против «атаки отражением» есть серьезный недостаток!Это предотвращает строительство evenInstance!Я думаю, вы должны изменить || на &&.Но это все еще не дает вам никаких гарантий, потому что «атака отражения» может быть между первым и вторым вызовом.Вы должны предварительно создать оба экземпляра во время загрузки класса, чтобы быть уверенными на 99%.

И если вы беспокоитесь об этом, вам определенно не следует реализовывать ни Cloneable, ни Serializable!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...