вторник, 20 октября 2015 г.

Выбор способа реализация синглтона с отложенной загрузкой в java


Постановка задачи 

И так, ты решил написать синглтон с отложенной инициализацией, тщательно всё проанализировал и понял, что оно тебе действительно нужно, поэтому не буду тебя от этого отговаривать. Я предполагаю, что всю не относящуюся к многопоточности лирику ты уже прочитал в других интернетах, вроде того что синглтон не должен иметь наследников, иметь закрытый конструктор и прочую требуху. И перед тобой неизбежно встала следующая проблема - ленивая инициализация предполагает, что возможна такая ситуация, когда ссылка на объект синглтона созданного в одном потоке, будет прочитана в другом, и по всем правилам хорошего тона нужно обеспечить корректную синхронизацию между записью и чтением сслылки на синглтон, иначе возникнет data race, поток прочитавший ссылку может обнаружить синглтон в недоинициализированном состоянии и прочии перепетии судьбы из серии кровь-кишки-распидарасило. Но и с этой проблемой ты успешно справился, потому что существует просто огромное количество статей на эту тему, в которых  детально разжёвывается, какие приёмы обеспечения синхронизации для синглтона работают, а какие не работают, не найти их ты не мог. Но если о чудо, это первая статья которая попалась на глаза, то прежде чем читать дальше, ознакомся на хабре с этим или этим, а если предпочитаете читать на английском то Safe Publication and Safe Initialization in Java. После того как ознакомишься у тебя останется всего только один вопрос:

- И так, я знаю как минимум три способа корректной и эффективной реализации синглтона с отложенной загрузкой, но какой-же черт возьми мне выбрать в моём конкретном случае?

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

Initialization on Demand Holder

public class Singleton {

 private Singleton() {}

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

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

}
Используйте Initialization on Demand Holder, когда в случае неудачи при конструировании синглтона вы не хотите давать ему второй шанс на инициализацию, а своему приложению нормально работать дальше. Любое Runtime исключение или Error вылетевшее из конструктора Singlton, приведёт к ошибке ExceptionInInitializerError в статическом инициализаторе класса SingltonHolder, и все последующие попытки вызова getInstance потерпят неудачу с ошибкой NoClassDefFoundError, так как виртуальная машина никогда не пробует инициализировать класс дважды. Разумно, такой способ использовать на пример в случае, когда синглтон представляет собой к примеру конфигурацию приложения создаваемую из properties файла, который должен находиться в classpath, если properties файла в classpath не оказалось, то будте уверены он там и не появится, последующие попытки создать синглтон всё равно потерпели бы неудачу,  единственное что остается это поднимать руки в верх и сдаваться.

Double-checked locking volatile holder

public class Singleton {

    private Singleton() {}

    private static volatile Singleton singleton;

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}
Используйте Double-checked locking volatile holder в случае если вы хотите дать синглтону второй шанс на инициализацию если при его конструировании произошла ошибка. Например синглтон представляет собой соединение с месседж брокером, и Вы хотите чтобы приложение могло продолжить работать если соединение установить не удалось, и попробовать установить его позже при следующих запросах в методе getInstance.

Double-checked locking final holder

public class Singleton {

    private Singleton() {}

    private static SingletonHolder holder;

    public static Singleton getInstance() {
        if (holder == null) {
            synchronized (Singleton.class) {
                if (holder == null) {
                    holder = new SingletonHolder(new Singleton());
                }
            }
        }
        return holder.instance;
    }

    private static final class SingletonHolder {

        public final Singleton instance;

        private SingletonHolder(Singleton instance) {
            this.instance = instance;
        }
    }

}
Используйте double-checked locking final holder, ровно по тем же причинам что и double-checked locking volatile holder.  Отказаться от идиомы с volatile в пользу final Вы можете, если считаете, что volatile может быть медленным, Вы это сами видели, или Вам ваша бабушка это рассказала, а она никогда не обманывает. В общем я бы, чтобы не тратить попусту время на препирания с коллегами, текущими или будущими, всегда бы по дефолту использовал Final Holder вместо Volatile Holder.

А корректен ли Double-checked locking final holder приведенный  выше?


Если у тебя появился этот вопрос, то значит ты не внимательно читал ссылки из первого абзаца, заодно вместе с ними рекомендуется повторить главу спецификации 17.5. final Field Semantics