English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Паттерн единичного экземпляра в Java

Java паттерн дизайна Singleton

Введение:

В процессе разработки программного обеспечения часто встречаются объекты, которые нам нужно использовать только один раз, например: пул потоков (threadpool), кэш (cache), диалоговые окна, предпочтения и т.д. Если создать несколько экземпляров таких объектов, это может привести к нежелательным проблемам, таким как аномальное поведение программы, чрезмерное использование ресурсов и т.д. В таких случаях паттерн Singleton может обеспечить наличие только одного экземпляра класса и предоставить глобальный точку доступа. Низ приведены примеры из простых классов Singleton, чтобы обсудить, какие методы можно использовать для реализации паттерна Singleton.

/**
 * Самый классический шаблон Singleton
 */
public class Singleton {
  // Установлен как статическая переменная для хранения уникального экземпляра Singleton
  private static Singleton singleInstance;
  private Singleton(){
    // Метод конструктора объявлен как частный, поэтому его можно вызвать только в классе Singleton
  }
  /*
   * Получение объекта Singleton, если объект еще не был создан, то создается новый объект и возвращается этот экземпляр
   */
  public static Singleton getInstance(){
    if (singleInstance == null) {
      singleInstance = new Singleton();
    }
    return singleInstance;
  }
  // другие методы
}

Из приведенного примера можно看出, что класс Singleton сам управляет процессом создания своего экземпляра и предоставляет глобальный точку доступа, то есть метод getInstance(), который в других классах, использующих Singleton, возвращает экземпляр. Одним из преимуществ этого шаблона Singleton является задержка создания экземпляра, то есть задержка инициализации. Иначе говоря, экземпляр создается только при необходимости, а не при загрузке класса. Это позволяет избежать浪费 производительности. Например, некоторые объекты могут не использоваться в начале выполнения программы или не использоваться вообще в процессе выполнения программы. Однако у этого примера есть и недостаток - он не线程 безопасен. Потому что если несколько потоков одновременно выполняют метод getInstance(), а экземпляр Singleton еще не создан, то все потоки будут думать, что singleInstance равен null, и создадут свои экземпляры Singleton. В результате будет создано несколько экземпляров Singleton, что явно не соответствует初衷 шаблона Singleton. Следующим шагом может быть улучшение этого шаблона

public class SingletonA {
  private static SingletonA singletongA;
  private SingletonA(){
  }
  /*
   * Добавление ключевого слова synchronized для того, чтобы метод getSingletonA стал синхронизированным
   */
  public static synchronized SingletonA getInstanceA(){
    if (singletongA == null) {
      singletongA = new SingletonA();
    }
    return singletongA;
  }
  // другие методы
}

Из этого примера видно, что добавление synchronized делает getInstanceA() синхронным методом, и thread должен ждать, пока другой thread не покинет этот метод, чтобы войти в него, что позволяет методу выполняться только одним thread в одно время.

Возможно, проблема решена, но следует знать, что синхронизация методов может влиять на эффективность выполнения программы. В этом примере мы просто решаем проблему, чтобы первый раз выполнение getInstance() не создавал несколько экземпляров, а в этом примере каждый раз при необходимости экземпляра вызывается getInstanceA() синхронный метод, а после создания экземпляра вызов synchronized становится избыточным, так как мы уже не должны беспокоиться о создании новых экземпляров singleton класса. Поэтому нам нужно сделать некоторые улучшения.

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

public class SingletonB {
  // создаем singleton в статическом инициализаторе (static initializer), гарантируя thread safety
  private static SingletonB singletonB = new SingletonB();
  private SingletonB(){
    // конструктор
  }
  public static SingletonB getInstaceB(){
    // уже инсталлирован, используйте его напрямую
    return singletonB;
  }
}

Верхний метод создает экземпляр при загрузке класса JVM, так как JVM создает экземпляр до доступа к нему threads, поэтому это безопасно с точки зрения thread. Но это может привести к浪费у ресурсов по сравнению с задержкой инсталляции. 而且 если такой класс большой, это может увеличить время инициализации программы.

Так можно ли использовать задержку инсталляции, не вызывая Race Condition и не уменьшая эффективность доступа? Давайте улучшим это с помощью двойной проверки блокировки.

/**
 * Двойная блокировка шаблона singleton
 */
public class SingletonC {
  private volatile static SingletonC singletonC;
  private SingletonC(){
  }
  public static SingletonC getInstanceC(){
    if (singletonC == null) {
      synchronized (SingletonC.class) {
        if (singletonC == null) {
          singletonC = new SingletonC();
        }
      }
    }
    return singletonC;
  }
}

Пример выше сначала проверяет экземпляр, если его нет, то enters синхронный блок, после того как enters синхронный блок, проверяет снова, если все еще null, то создает экземпляр, поэтому singletonC = new SingletonC() будет выполняться только один раз, а после вызова getInstanceC() сразу возвращается экземпляр, поэтому кроме первого вызова, не будет так, как в втором примере, каждый раз вызывать синхронный метод. Таким образом, можно уменьшить время выполнения getInstanceC(). Наверное, вы заметите, что есть ключевое слово volatile, его функция - после инициализации singletonC он становится видимым для всех потоков, несколько потоков могут правильно обрабатывать эту переменную SingletonC. Но следует отметить: ключевое слово volatile можно использовать только в Java 5 и более поздних версиях, если до этой версии, это может привести к сбою двойной проверки.

При использовании паттерна Singleton, если есть несколько классовых загрузчиков (classloader), необходимо自行 определить классовый загрузчик и определить, что использовать один классовый загрузчик. Porque cada классовый загрузчик определяет пространство имен, разные классовые загрузчики могут загружать одну и ту же класс, что может привести к созданию нескольких экземпляров singleton класса.

Спасибо за чтение, я надеюсь, что это поможет вам, спасибо за поддержку нашего сайта!

Заявление: содержание этой статьи взято из интернета, авторские права принадлежат их авторам. Контент предоставлен пользователями интернета и загружен ими самостоятельно. Этот сайт не owns авторские права, не делает ручную редактуру и не несет ответственности за связанные с этим юридические вопросы. Если вы обнаружите содержание,涉嫌侵犯版权, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (во время отправки письма замените # на @), и предоставьте соответствующие доказательства. При подтверждении侵权事实, этот сайт немедленно удалят涉嫌侵权的内容。

Рекомендуется к просмотру