Двойная проверка блокировки с помощью Singleton

Двойная проверка блокировки с синглтоном

1. Вступление

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

Давайте посмотрим, как это работает.

2. Реализация

Для начала рассмотрим простой синглтон с драконовской синхронизацией:

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

    // private constructor and other methods ...
}

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

Чтобы исправить это,we could instead start by verifying if we need to create the object in the first place and only in that case we would acquire the lock.

Далее, мы хотим выполнить ту же самую проверку снова, как только мы войдем в синхронизированный блок, чтобы сохранить операцию на атомарном уровне:

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

    // private constructor and other methods...
}

При использовании этого шаблона следует иметь в виду, чтоthe field needs to be volatile предотвращает проблемы с несогласованностью кеша. Фактически, модель памяти Java позволяет публиковать частично инициализированные объекты, что может, в свою очередь, привести к незначительным ошибкам.

3. альтернативы

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

  • поскольку для правильной работы требуется ключевое словоvolatile, оно несовместимо с Java 1.4 и более ранними версиями.

  • он довольно подробный и затрудняет чтение кода

По этим причинам давайте рассмотрим некоторые другие варианты без этих недостатков. Все следующие методы делегируют задачу синхронизации в JVM.

3.1. Ранняя инициализация

Самый простой способ обеспечить безопасность потоков - встроить создание объекта или использовать эквивалентный статический блок. Это использует тот факт, что статические поля и блоки инициализируются один за другим (Java Language Specification 12.4.2):

public class EarlyInitSingleton {
    private static final EarlyInitSingleton INSTANCE = new EarlyInitSingleton();
    public static EarlyInitSingleton getInstance() {
        return INSTANCE;
    }

     // private constructor and other methods...
}

3.2. Инициализация по запросу

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

public class InitOnDemandSingleton {
    private static class InstanceHolder {
        private static final InitOnDemandSingleton INSTANCE = new InitOnDemandSingleton();
    }
    public static InitOnDemandSingleton getInstance() {
        return InstanceHolder.INSTANCE;
    }

     // private constructor and other methods...
}

В этом случае классInstanceHolder назначит поле при первом доступе к нему, вызвавgetInstance.

3.3. Enum Singleton

Последнее решение взято из книгиEffective Java (пункт 3) Джошуа Блока и используетenum вместоclass. На момент написания, это считается наиболее кратким и безопасным способом написания синглтона:

public enum EnumSingleton {
    INSTANCE;

    // other methods...
}

4. Заключение

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

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

Как всегда, код всех примеров -available on GitHub.