Сколько способов написать одноэлементный шаблон в интервью?

интервью Java задняя часть Безопасность

«Вы знаете, как пишется слово «фенхель» для бобов укропа?»

Есть ли несколько полезных способов написать запутанный одноэлементный шаблон? Это несколько полезно. На собеседованиях один или несколько из этих методов письма часто выбирают в качестве начала разговора. При изучении шаблонов проектирования и стилей кодирования легко перейти к другим вопросам. Вот несколько наиболее часто используемых способов письма для обезьян, но не пытайтесь запомнить «метод письма бобов укропа».Самая большая радость в программировании“know everything, control everything”.

Версия JDK: оракул java 1.8.0_102

Его можно условно разделить на 4 категории, а их основные формы, варианты и характеристики описаны ниже.

полный мужской режим

Удовлетворение — это одноэлементный шаблон с наибольшим количеством вариантов. Мы начинаем с богача, и постепенно разбираемся в вопросах, на которые необходимо обратить внимание при реализации паттерна singleton через его варианты.

обычный толстяк

Сытый человек означает, что он сыт, он не спешит лишний раз есть и ест, когда голоден. Таким образом, он не инициализирует синглтон сначала, а затем инициализирует его при первом использовании, то есть“懒加载”.

// 饱汉
// UnThreadSafe
public class Singleton1 {
  private static Singleton1 singleton = null;

  private Singleton1() {
  }

  public static Singleton1 getInstance() {
    if (singleton == null) {
      singleton = new Singleton1();
    }
    return singleton;
  }
}

Суть режима сатаны — отложенная загрузка. Преимущество в том, что он запускается быстрее и экономит ресурсы, синглтон не нужно инициализировать до первого обращения к экземпляру, небольшой недостаток в том, что его сложно писать, а главный недостаток в том, что поток не безопасно, а оператор if имеет условия гонки.

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

Полный мужчина - Вариант 1

Самым жестоким нарушением является модификация метода getInstance() ключевым словом synchronized, что может обеспечить абсолютную безопасность потоков.

// 饱汉
// ThreadSafe
public class Singleton1_1 {
  private static Singleton1_1 singleton = null;

  private Singleton1_1() {
  }

  public synchronized static Singleton1_1 getInstance() {
    if (singleton == null) {
      singleton = new Singleton1_1();
    }
    return singleton;
  }
}

Преимущество варианта 1 в том, что он прост в написании и абсолютно потокобезопасен; недостаток в том, что производительность параллелизма крайне плохая, по сути, он полностью вырождается в последовательный. Синглтон нужно инициализировать только один раз, но даже после инициализации нельзя избежать синхронизированной блокировки, поэтому getInstance() полностью стала последовательной операцией.Рекомендуется использовать в сценариях, нечувствительных к производительности..

Полный мужчина - Вариант 2

Вариант 2 - "пресловутый"DCL 1.0.

Ввиду проблемы невозможности избежать блокировки после одноэлементной инициализации в варианте 1, вариант 2 имеет еще один слой проверки во внешнем слое варианта 1, плюс проверка во внутреннем слое синхронизированного, т. е. "Double Check Lock" (Двойная проверка блокировки), именуемая DCL).

// 饱汉
// UnThreadSafe
public class Singleton1_2 {
  private static Singleton1_2 singleton = null;

  private Singleton1_2() {
  }

  public static Singleton1_2 getInstance() {
    // may get half object
    if (singleton == null) {
      synchronized (Singleton1_2.class) {
        if (singleton == null) {
          singleton = new Singleton1_2();
        }
      }
    }
    return singleton;
  }
}

Ядром варианта 2 является DCL, и кажется, что вариант 2 достиг желаемого эффекта: ленивая загрузка + безопасность потоков. К сожалению, как указано в комментариях, DCL по-прежнему небезопасен для потоков, и вы можете получить «половину объекта» из-за переупорядочения инструкций. После подробного прочтения Варианта 3 вы можете обратиться к предыдущей статье Monkey, которая не будет повторяться здесь.

Ссылаться на:Роль и принцип ключевого слова volatile

Полный мужчина - Вариант 3

Вариант 3 конкретно нацелен на вариант 2, который можно описать какDCL 2.0.

Чтобы решить проблему "полуобъекта" варианта 3, в варианте 3 к экземпляру добавляется ключевое слово volatile. Принцип см. в приведенной выше ссылке.

// 饱汉
// ThreadSafe
public class Singleton1_3 {
  private static volatile Singleton1_3 singleton = null;

  private Singleton1_3() {
  }

  public static Singleton1_3 getInstance() {
    if (singleton == null) {
      synchronized (Singleton1_3.class) {
        // must be a complete instance
        if (singleton == null) {
          singleton = new Singleton1_3();
        }
      }
    }
    return singleton;
  }
}

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

режим голодного человека

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

// 饿汉
// ThreadSafe
public class Singleton2 {
  private static final Singleton2 singleton = new Singleton2();

  private Singleton2() {
  }

  public static Singleton2 getInstance() {
    return singleton;
  }
}

Преимущество Hungry заключается в том, что он по своей природе потокобезопасен (благодаря механизму загрузки классов), его очень просто писать и при его использовании нет задержки; недостатком является то, что он может привести к пустой трате ресурсов (если singleton никогда не используется после загрузки класса).

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

Режим держателя

Мы не только надеемся воспользоваться удобством и потокобезопасностью статических переменных в голодном режиме, но также надеемся избежать траты ресурсов за счет отложенной загрузки. Режим Holder удовлетворяет этим двум требованиям: ядро ​​по-прежнему составляют статические переменные, которые достаточно удобны и потокобезопасны; статический класс Holder содержит реальные экземпляры, что косвенно реализует ленивую загрузку.

// Holder模式
// ThreadSafe
public class Singleton3 {
  private static class SingletonHolder {
    private static final Singleton3 singleton = new Singleton3();

    private SingletonHolder() {
    }
  }


  private Singleton3() {
  }

  /**
  * 勘误:多写了个synchronized。。
  public synchronized static Singleton3 getInstance() {
    return SingletonHolder.singleton;
  }
  */

  public static Singleton3 getInstance() {
    return SingletonHolder.singleton;
  }
}

По сравнению с режимом Hungry Man, режим Hold только увеличивает стоимость статического внутреннего класса, что сравнимо с вариантом 3 Satisfied Man (немного лучше) и является более популярной реализацией.Также рекомендуется учитывать.

режим перечисления

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

базовая нумерация

Сделайте статическую переменную-член перечисления экземпляром синглтона:

// 枚举
// ThreadSafe
public enum Singleton4 {
  SINGLETON;
}

Объем кода меньше, чем в режиме Hungry Man. Но пользователь может получить доступ только к экземпляру напрямуюSingleton4.SINGLETON- На самом деле, этот метод доступа также подходит для использования в качестве синглтона, но за счет преимуществ статических фабричных методов, таких как невозможность добиться отложенной загрузки.

Уродливый, но полезный синтаксический сахар

Перечисления Java — это «уродливый, но полезный синтаксический сахар».

Суть перечисляемого одноэлементного паттерна

Декомпилируя (jad,Исходный код|Оптимизация операции конкатенации строк "+"?Тоже используется) Открываешь синтаксический сахар, там видна суть типа перечисления, которое упрощенно выглядит следующим образом:

// 枚举
// ThreadSafe
public class Singleton4 extends Enum<Singleton4> {
  ...
  public static final Singleton4 SINGLETON = new Singleton4();
  ...
}

По сути, то же самое, что и голодный китайский режим, единственное отличие состоит в общедоступных статических переменных-членах.

Реализовать некоторые трюки с перечислениями

Эта часть не имеет ничего общего с синглтонами и может быть пропущена. Если вы решите прочитать, обратите внимание на этот факт:Хотя перечисления достаточно гибкие, их правильное использование может быть затруднено.. Типичным достаточно простым примером является класс TimeUnit, и рекомендуется успеть его терпеливо прочитать.

Как вы видели выше, сущность перечисляемого синглтона по-прежнему остается обычным классом. На самом деле, мы можем сделать это на перечислимых синглтонах.УвеличиватьВсе обычный класс может сделать. Дело в том, что инициализация экземпляра перечисления может быть понята как создание анонимного внутреннего класса. Быть более очевидным, мы находимся вSingleton4_1Определите обычную закрытую переменную-член, обычный общедоступный метод-член и общедоступный абстрактный метод-член следующим образом:

// 枚举
// ThreadSafe
public enum Singleton4_1 {
  SINGLETON("enum is the easiest singleton pattern, but not the most readable") {
    public void testAbsMethod() {
      print();
      System.out.println("enum is ugly, but so flexible to make lots of trick");
    }
  };

  private String comment = null;

  Singleton4_1(String comment) {
    this.comment = comment;
  }

  public void print() {
    System.out.println("comment=" + comment);
  }

  abstract public void testAbsMethod();


  public static Singleton4_1 getInstance() {
    return SINGLETON;
  }
}

Таким образом, класс перечисленияSingleton4_1Каждый экземпляр перечисления не только наследуется от родительского классаSingleton4_1членский методprint(), также должен реализовать родительский классSingleton4_1Абстрактный метод членаtestAbsMethod().

Суммировать

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

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

Метод реализации ключевой момент Пустая трата ресурсов потокобезопасность Производительность многопоточной среды достаточно оптимизирована
основная сытость ленивая загрузка нет нет -
Очень разнообразие 1 Ленивая загрузка, синхронизация нет да нет
полный мужчина вариант 2 Ленивая загрузка, DCL нет нет -
полный мужской вариант 3 Ленивая загрузка, DCL, volatile нет да да
голодный человек Инициализация статической переменной да да да
Holder Инициализация статической переменной, держатель нет да да
перечислить Суть перечисления, инициализация статической переменной нет да да

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


Ссылка на эту статью:Сколько способов написать одноэлементный шаблон в интервью?
автор:обезьяна 007
Источник:monkeysayhi.github.io
Эта статья основана наCreative Commons Attribution-ShareAlike 4.0Международное лицензионное соглашение выпущено, его можно перепечатать, вывести или использовать в коммерческих целях, но авторство и ссылка на эту статью должны быть сохранены.