Используйте перечисления для написания более элегантных одноэлементных шаблонов проектирования.

Шаблоны проектирования
Используйте перечисления для написания более элегантных одноэлементных шаблонов проектирования.

В шаблоне одноэлементного проектирования в Java много раз мы замечали только поверхностные проблемы, вызванные потоками, но мы не рассматривали ограничения механизма отражения.Эта статья призвана кратко представить уязвимость использования перечисления для предотвращения отражения.

Самый обычный синглтон

Давайте сначала покажем кусок самого обычного ленивого синглтона:

public class Singleton {

    private Singleton(){} // 私有构造

    private static Singleton instance = null; // 私有单例对象

    // 静态工厂
    public static Singleton getInstance(){
        if (instance == null) { // 双重检测机制
            synchronized (Singleton.class) { // 同步锁
                if (instance == null) { // 双重检测机制
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

Приведенный выше синглтон написан с использованиеммеханизм двойной проверкиДобавил некоторую долю безопасности, но не учелИзменение порядка инструкций для компилятора JVM.

2. Устранить влияние перестановки инструкций JVM на синглтоны.

1. Что такое перестройка инструкций?

Например, простой экземпляр = новый синглтон в Java будет скомпилирован как следующие инструкции JVM.

memory =allocate();    //1:分配对象的内存空间 

ctorInstance(memory);  //2:初始化对象 

instance =memory;     //3:设置instance指向刚分配的内存地址

Однако порядок этих инструкций не является статическим и может быть оптимизирован JVM и ЦП, а инструкции переупорядочены в следующем порядке:

memory =allocate();    //1:分配对象的内存空间 

instance =memory;     //3:设置instance指向刚分配的内存地址 

ctorInstance(memory);  //2:初始化对象

2. Влияние

В соответствии с приведенным выше шаблоном синглтона возникнут следующие проблемы:

  1. Когда поток A завершает выполнение 1 и 3, он готов перейти к 2, то есть объект-экземпляр еще не инициализирован, но уже не указывает на null.

  2. В это время, если поток B вытесняет ресурсы ЦП, результат выполнения if (instance == null) будет ложным,

  3. тем самымВозвращает экземпляр объекта, который не был инициализирован.

3. Решить

Как предотвратить это очень просто.Вы можете использовать ключевое слово volatile для изменения объекта экземпляра, как показано на следующем рисунке для оптимизации:

Зачем?

Проще говоря, роль модификатора volatile здесь состоит в том, чтобы предотвратить переупорядочивание инструкций до и после доступа к переменной, тем самым обеспечивая порядок выполнения инструкций.

Это означает, что порядок выполнения инструкций строго соответствует пунктам 1, 2 и 3 выше, чтобы объект не оказался в промежуточном состоянии.

На самом деле, волатильное ключевое слово широко используется в многопоточной разработке, поэтому я сейчас не буду в деталях.

Отлично, но все же здесьНе учитывает влияние механизмов отражения.

3. Расширенные статьи для достижения идеального единичного экземпляра

1. Маленький эпизод

Существует множество шаблонов для реализации синглетонов, вот способ реализации шаблона синглтона с использованием статических внутренних классов:

public class Singleton {

    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton (){}

    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }

}

Это очень умный способ, оригинал такой:

  1. К статическому внутреннему классу LazyHolder нельзя получить доступ извне, а одноэлементный объект INSTANCE можно получить только при вызове метода Singleton.getInstance().

  2. Инициализация объекта INSTANCE происходит не тогда, когда загружается одноэлементный класс Singleton, а когда вызывается метод getInstance, так что загружается статический внутренний класс LazyHolder.

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

2. Отображение уязвимостей

Многие типы одноэлементных методов записи имеют общую проблему, то есть они не могут предотвратить лазейки в механизме отражения, так что уникальность объекта не может быть гарантирована.Следующий пример:

Использование следующего арктивного кода создано для единственного примера вышеуказанной структуры.

public static void main(String[] args) {

    try {

        //获得构造器
        Constructor con = Singleton.class.getDeclaredConstructor();

        //设置为可访问
        con.setAccessible(true);

        //构造两个不同的对象
        Singleton singleton1 = (Singleton)con.newInstance();
        Singleton singleton2 = (Singleton)con.newInstance();

        //验证是否是不同对象
        System.out.println(singleton1);
        System.out.println(singleton2);
        System.out.println(singleton1.equals(singleton2));
    } catch (Exception e) {
        e.printStackTrace();
    }

}

Давайте посмотрим непосредственно на результаты:

Оказывается, это явно два объекта.

3. Решить

использоватьперечислитьДля достижения однократного режима.

Реализация очень проста, всего три строчки кода:

public enum Singleton {
    INSTANCE;
}

Тот, что показан выше, является синглтоном,

Зачем?

По сути, это синтаксический сахар для enum,JVM предотвращает отражение от получения частных конструкторов классов перечисления.

По-прежнему используйте приведенный выше код отражения для тестирования, поиска и сообщения об ошибках. Эй, это прекрасно решает проблему отражения.

4, недостатки

Метод использования перечисления играет роль синглтона, но и у него есть недостаток,

Это Не могу сделать ленивую загрузку.

Оригинальный адрес:woooooo.jet Chen.talent/Java-сингл…