[Серия «Учимся вместе»] Одиночный режим: рекомендуется только три~

Шаблоны проектирования
[Серия «Учимся вместе»] Одиночный режим: рекомендуется только три~

намерение

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

Псевдоним: шаблон Singleton

Рождение одноэлементного паттерна

[Разработка]: Босс, почему я сохраняю информацию о конфигурации каждый раз, когда она отличается от того, что я ожидал, и она всегда перезаписывается?

[БОСС]: А? Я взгляну.

[BOSS]: Вы создаете новый объект конфигурации каждый раз, когда используете его?

[Разработчик]: Да, в чем проблема?

[БОСС]: Это точно не так, во всем мире должна быть только одна информация о конфигурации, иначе они будут влиять друг на друга!

Основной код HeadFirst

Голодный тип (не рекомендуется)

public class HazardousTypeSingleton {

    private static final App APP = new App();

    // 私有构造方法
    private HazardousTypeSingleton () {}

    // 类加载时已初始化,不会有多线程的问题
    static App getInstance() {
        System.out.println("APP - 饿汉型模式");
        return APP;
    }
}

Происхождение названия: Поскольку он загружается с загрузкой класса, он очень «срочный», поэтому его называют типом голодного человека.

**Оценка:** Между этим способом записи и глобальными переменными нет существенной разницы, и это не рекомендуется.

Ленивый тип (не рекомендуется)

public class LazyTypeSingleton {

    private LazyTypeSingleton () {}

    // 静态私用成员,没有初始化
    private static App intance = null;

    /***
     * 直接加synchronized关键字
     */
    synchronized  static App getIntance () {
        System.out.println("APP - 懒汉型模式");
        if (null == intance) {
            intance = new App();
            return intance;
        }
        return intance;
    }
}

Происхождение имени: он загружается при вызове, поэтому его называют ленивым типом.

**Оценка:** Этот способ записи имеет функцию отложенной загрузки, но добавляется большая синхронизированная блокировка, поэтому он менее эффективен в многопоточной среде.

Ленивая проверка двойной блокировки 🧡

public class LazyTypeSingleton {

    // volatile关键字修饰,防止指令重排
    private volatile static App app = null;

    /***
     * Double Check Lock(DCL) 双重锁校验
     */
    static App getInstanceByDCL () {
        if (null == app) {
            synchronized (LazyTypeSingleton.class) {
                if (null == app) {
                    System.out.println("APP - 饿汉模式DCL 双重锁校验");
                    app = new App();
                    return app;
                }
            }
        }
        return app;
    }
}

Обратите внимание на роль ключевого слова volatile Подробнее см.: https://juejin.cn/post/6844904155069284359.

**Оценка: **Рекомендуемый метод записи может обеспечить безопасность потоков и приводит к отложенной загрузке.

Статический метод внутреннего класса 🧡

public class InnterTypeSingleton {

    private InnterTypeSingleton(){
        throw new IllegalStateException();
    }

    // 静态内部类方式,类似饿汉保证天然的线程安全
    private static class SingletonHolder{
        private final static App app = new App();
    }

    static App getInstance(){
        System.out.println("APP - 静态内部类方式(Holder)");
        return SingletonHolder.app;
    }
}

** Оценка: ** Потокобезопасность, высокая эффективность вызовов, может задерживать загрузку

Волшебная ошибка статического внутреннего класса

public class InnterTypeSingletonError {

    private InnterTypeSingletonError(){
        System.out.println(5 / 0);
    }

    private static class SingletonHolder{
        private final static InnterTypeSingletonError app = new InnterTypeSingletonError();
    }

    static InnterTypeSingletonError getInstance(){
        System.out.println("APP - 静态内部类方式(Holder)");
        return SingletonHolder.app;
    }

    public static void main(String[] args){
        try {
            InnterTypeSingletonError.getInstance();
        } catch (Throwable t) {
            t.printStackTrace();
        }

        try {
            InnterTypeSingletonError.getInstance();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

Обратите внимание на блок кода выше:

private InnterTypeSingletonError(){
 System.out.println(5 / 0);
}

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

APP - 静态内部类方式(Holder)
APP - 静态内部类方式(Holder)
java.lang.ExceptionInInitializerError
	at com.design.singleton.InnterTypeSingletonError.getInstance(InnterTypeSingletonError.java:23)
	at com.design.singleton.InnterTypeSingletonError.main(InnterTypeSingletonError.java:28)
Caused by: java.lang.ArithmeticException: / by zero
	at com.design.singleton.InnterTypeSingletonError.<init>(InnterTypeSingletonError.java:14)
	at com.design.singleton.InnterTypeSingletonError.<init>(InnterTypeSingletonError.java:11)
	at com.design.singleton.InnterTypeSingletonError$SingletonHolder.<clinit>(InnterTypeSingletonError.java:18)
	... 2 more
java.lang.NoClassDefFoundError: Could not initialize class com.design.singleton.InnterTypeSingletonError$SingletonHolder
	at com.design.singleton.InnterTypeSingletonError.getInstance(InnterTypeSingletonError.java:23)
	at com.design.singleton.InnterTypeSingletonError.main(InnterTypeSingletonError.java:34)

Можно обнаружить, что первая ошибка является обычным исключением, и если вторая ошибка снова сообщается, это Не удалось инициализировать класс, почему?

так как:"При загрузке класса статические переменные будут инициализированы только при первой загрузке, после этого они не будут инициализированы во второй раз, независимо от того, успешны они или нет."

Так что будьте осторожны при использовании

Метод перечисления 🧡

public enum  EnumSingleton {
    /***
     * APP对象
     */
    APP;

    private App app;

    EnumSingleton() {
        app = new App();
    }

    public App getInstance() {
        System.out.println("**************************");
        System.out.println("APP - 枚举方式");
        return app;
    }
}

**Оценка:** Потокобезопасность, высокая эффективность вызовов, отсутствие задержки загрузки, естественное предотвращение вызовов отражения и десериализации.

какие сценарии применяются

Шаблон singleton можно использовать в следующих ситуациях:

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

Код / практическое применение в жизни

Во многих проектах очень распространены пулы соединений с базами данных или центры конфигурации, объекты файла конфигурации и т. д. ~

Суммировать

Благодаря статье Java3Y:Три криво пиши Жучка пиши плачь, из которого я узнал об ошибке артефакта при использовании внутреннего класса

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

  • Ленивый тип, основанный на проверке двойной блокировки
  • статический внутренний класс
  • метод перечисления

Связанные ссылки на код

Адрес GitHub

  • С учетом кейсов в двух классических книгах "HeadFirst" и "GOF"
  • Предоставляет дружественное руководство по чтению

break-word; цвет: #664D9D; вес шрифта: обычный; нижняя граница: 1px сплошная #664D9D;">mdnice типографика