Постановка задачи
И так, ты решил написать синглтон с отложенной инициализацией, тщательно всё проанализировал и понял, что оно тебе действительно нужно, поэтому не буду тебя от этого отговаривать. Я предполагаю, что всю не относящуюся к многопоточности лирику ты уже прочитал в других интернетах, вроде того что синглтон не должен иметь наследников, иметь закрытый конструктор и прочую требуху. И перед тобой неизбежно встала следующая проблема - ленивая инициализация предполагает, что возможна такая ситуация, когда ссылка на объект синглтона созданного в одном потоке, будет прочитана в другом, и по всем правилам хорошего тона нужно обеспечить корректную синхронизацию между записью и чтением сслылки на синглтон, иначе возникнет 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