Подтвердить что ты не робот

Создание экземпляра Singleton

Ниже показано создание объекта singleton.

public class Map_en_US extends mapTree {

    private static Map_en_US m_instance;

    private Map_en_US() {}

    static{
        m_instance = new Map_en_US();
        m_instance.init();
    }

    public static Map_en_US getInstance(){
        return m_instance;
    }

    @Override
    protected void init() {
        //some code;
    }
}

Мой вопрос в том, что является причиной использования статического блока для создания экземпляра. я знаком с приведенной ниже формой создания одноэлементности.

public static Map_en_US getInstance(){
    if(m_instance==null)
      m_instance = new Map_en_US();
}
4b9b3361

Ответ 1

Причина безопасность потока.

Форма, о которой вы говорили, вы знакомы с потенциалом инициализации синглтона много раз. Более того, даже после того, как он был инициализирован несколько раз, будущие вызовы getInstance() разными потоками могут возвращать разные экземпляры! Кроме того, один поток может видеть частично инициализированный экземпляр singleton! (предположим, что конструктор подключается к БД и проверяет подлинность: один поток может получить ссылку на синглтон до того, как произойдет аутентификация, даже если это сделано в конструкторе!)

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

  • Concurrency: они должны потенциально выполняться одновременно;

  • Видимость: изменения в памяти, сделанные одним потоком, могут быть недоступны для других потоков;

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

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

Статический блок гарантируется JVM, который должен выполняться только один раз (если вы не загружаете и не инициализируете класс с использованием разных ClassLoader s, но детали не входят в сферу этого вопроса, я бы сказал) и только одним потоком, и его результаты гарантированно будут видны для каждого другого потока.

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

Мой предпочтительный шаблон: потокобезопасный и ленивый

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

Если вы хотите, вы можете использовать следующий шаблон:

public class Singleton {
  private Singleton() { ... }
  private static class SingletonHolder {
    private static final Singleton instance = new Singleton();
  }
  public static Singleton getInstance() {
    return SingletonHolder.instance;
  }
}

В приведенном выше коде singleton будет создан только при инициализации класса SingletonHolder. Это произойдет только один раз (если, как я уже говорил, вы используете несколько ClassLoaders), код будет выполняться только одним потоком, результаты не будут иметь проблем с видимостью, а инициализация будет выполняться только при первой ссылке на SingletonHolder, который происходит внутри метода getInstance(). Это шаблон, который я использую чаще всего, когда мне нужен синглтон.

Другие шаблоны...

1. synchronized getInstace()

Как обсуждалось в комментариях к этому ответу, существует еще один способ реализовать одноэлементный поток в потоковом безопасном режиме и который почти совпадает с (сломанным), с которым вы знакомы:

public class Singleton {
  private static Singleton instance;
  public static synchronized getInstance() {
    if (instance == null)
      instance = new Singleton();
  }
}

Приведенный выше код гарантирует, что модель памяти будет потокобезопасной. Спецификация JVM заявляет следующее (более загадочным образом): пусть L - это блокировка любого объекта, пусть T1 и T2 - два потока. Выпуск L на T1 происходит до получения L на T2.

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

Итак, предположим, что T1 - это первый поток, который ввел метод getInstance(). Пока это не закончится, ни один другой поток не сможет ввести тот же метод (поскольку он синхронизирован). Он увидит, что instance имеет значение null, создаст экземпляр Singleton и сохранит его в поле. Затем он отпустит блокировку и вернет экземпляр.

Затем T2, ожидавший блокировку, сможет его получить и ввести метод. Поскольку он приобрел тот же самый замок, который только что выпустил T1, T2 увидит, что поле instance содержит тот же самый экземпляр Singleton, созданный T1, и просто вернет его. Более того, инициализация синглтона, которая была выполнена T1, произошла до выпуска блокировки на T1, которая произошла до получения блокировки на T2, поэтому нет способа, чтобы T2 мог видеть частично инициализированный синглтон.

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

2. Двойная проверка блокировки (DCL)

Часто люди боятся стоимости покупки замка. Я читал, что в настоящее время это не так актуально для большинства приложений. Реальная проблема с фиксацией блокировки заключается в том, что она повреждает масштабируемость путем сериализации доступа к синхронизированному блоку.

Кто-то разработал простой способ избежать приобретения блокировки, и он был назван дважды проверенной блокировкой. Проблема в том, что большинство реализаций нарушены. То есть, большинство реализаций не являются потокобезопасными (т.е. Являются небезопасными как метод getInstace() в исходном вопросе).

Правильный способ реализации DCL выглядит следующим образом:

public class Singleton {
  private static volatile Singleton instance;
  public static Singleton getInstance() {
    if (instance == null) {
      synchronized {
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}

Разница между этой правильной и неправильной реализацией - это ключевое слово volatile.

Чтобы понять, почему, T1 и T2 являются двумя потоками. Предположим сначала, что поле не изменчиво.

T1 входит в метод getInstace(). Это первый, кто когда-либо вводит его, поэтому поле равно null. Затем он входит в синхронизированный блок, затем второй, если. Он также оценивает значение true, поэтому T1 создает новый экземпляр синглтона и сохраняет его в поле. Затем блокировка освобождается, а синглтон возвращается. Для этого потока гарантируется, что Singleton полностью инициализирован.

Теперь T2 входит в метод getInstace(). Возможно (хотя и не гарантировано), что он увидит, что instance != null. Затем он пропустит блок if (и, следовательно, никогда не получит блокировку), и будет напрямую возвращать экземпляр Singleton. Из-за переупорядочения возможно, что T2 не увидит всю инициализацию, выполняемую Singleton в своем конструкторе! Повторяя пример одноканального соединения db, T2 может видеть подключенный, но еще не аутентифицированный синглтон!

Для получения дополнительной информации...

... Я бы рекомендовал блестящую книгу Java Concurrency на практике, а также спецификацию языка Java.

Ответ 2

Если вы инициализируетесь в методе getInstance(), вы можете получить гоночные условия, то есть, если два потока выполняют проверку if(m_instance == null) одновременно, оба могут видеть, что экземпляр имеет значение null, и, следовательно, оба могут вызвать m_instance = new Map_en_US();

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

Вот хороший обзор.

Ответ 3

Как насчет этого подхода для уничтожения статического блока:

private static Map_en_US s_instance = new Map_en_US() {{init();}};

Он делает то же самое, но способ опережает.

Объяснение этого синтаксиса:
Внешний набор фигурных скобок создает анонимный класс. Внутренний набор брекетов называется "блок-блок" - он срабатывает во время строительства.
Этот синтаксис часто некорректно называют синтаксисом "double brace initializer", обычно теми, кто не понимает, что происходит.

Также обратите внимание:
m_ - префикс условного обозначения именования, например, (т.е. член).
s_ - префикс условного обозначения для полей класса (т.е. статического).
Поэтому я изменил имя поля на s_....

Ответ 4

Это зависит от того, насколько ресурсоемким является метод init. Если это, например, делает много работы, возможно, вы хотите, чтобы эта работа выполнялась при запуске приложения, а не при первом вызове. Может быть, он загружает карту из Интернета? Я не знаю...

Ответ 5

Статический блок выполняется, когда класс сначала загружается JVM. Как сказал Бруно, это помогает в предотвращении потоков, потому что нет возможности, чтобы два потока сражались за один и тот же вызов getInstance() в первый раз.

Ответ 6

  • Со статическим экземпляром будет только одна копия экземпляра для каждого класса независимо от количества создаваемых объектов.
  • Второе преимущество заключается в том, что метод thread-safe, поскольку вы ничего не делаете в этом методе, кроме возвращения экземпляра.

Ответ 7

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

Используя метод getInstance(), объект для класса строится и инициализируется при вызове метода, а не в статической инициализации. И не очень безопасно, если вы одновременно запускаете getInstance() в разных потоках.

Ответ 8

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

public class Map_en_US extends mapTree {

    private static
            /* thread safe without final,
               see VM spec 2nd ed 2.17.15 */
            Map_en_US m_instance = createAndInit();

    private Map_en_US() {}

    public static Map_en_US getInstance(){
        return m_instance;
    }

    @Override
    protected void init() {
        //some code;
    }

    private static Map_en_US createAndInit() {
        final Map_en_US tmp = new Map_en_US();
        tmp.init();
        return tmp;
    }
}

обновление исправлено за спецификация VM 2.17.5, подробнее в комментариях

Ответ 9

    // Best way to implement the singleton class in java
    package com.vsspl.test1;

    class STest {

        private static STest ob= null;
        private  STest(){
            System.out.println("private constructor");
        }

        public static STest  create(){

            if(ob==null)
                ob = new STest();
            return ob;
        }

        public  Object  clone(){
            STest   obb = create();
            return obb;
        }
    }

    public class SingletonTest {
        public static void main(String[] args)  {
            STest  ob1 = STest.create();
            STest  ob2 = STest.create();
            STest  ob3 = STest.create();

            System.out.println("obj1  " +  ob1.hashCode());
            System.out.println("obj2  " +  ob2.hashCode());
            System.out.println("obj3  " +  ob3.hashCode());


            STest  ob4 = (STest) ob3.clone();
            STest  ob5 = (STest) ob2.clone();
            System.out.println("obj4  " +  ob4.hashCode());
            System.out.println("obj5  " +  ob5.hashCode());

        }
    }

-------------------------------- OUT PUT -------------------------------------
private constructor
obj1  1169863946
obj2  1169863946
obj3  1169863946
obj4  1169863946
obj5  1169863946

Ответ 10

Интересный никогда не видел этого раньше. Похоже, в основном предпочтение стиля. Я полагаю, что одно отличие: статическая инициализация происходит при запуске VM, а не в первом запросе для экземпляра, потенциально исключающем проблему с параллельными экземплярами? (Который также может обрабатываться с объявлением метода synchronized getInstance())