Решает ли этот код проблему блокировки с двойной проверкой в ​​Java?

StackOverflow https://stackoverflow.com/questions/5976151

Вопрос

Решает ли этот код проблему блокировки с двойной проверкой в ​​Java?

public class DBAccessService() {
    private static DBAccessService INSTANCE;  

    private DBAccessService() {}

    public static DBAccessService getInstance() {
        if (INSTANCE != null) {
            return INSTANCE;
        }
        return createInstance();
    }

    private static synchronized DBAccessService createInstance() {
        if (INSTANCE != null) {
            return INSTANCE;
        }
        DBAccessService instance = new DBAccessService();
        INSTANCE = instance;

        return INSTANCE;
    }
}

Есть 2 аспекта, на которые следует обратить внимание:

  1. getInstance() является нет синхронизировано, поэтому после инициализации INSTANCE синхронизация не требует затрат.
  2. createInstance() синхронизирован

Итак, вопрос:есть ли у этого кода какие-либо проблемы?Законно ли это и всегда потокобезопасный?

Это было полезно?

Решение

Для решения этого конкретного вопроса Параллелизм Java на практике (написано командой, которая, по сути, написала библиотеку java.util.concurrent) рекомендует Идиома класса держателя ленивой инициализации (в моем экземпляре страница 348, листинг 16.6, а не 16.7)

@ThreadSafe
public class DBAccessServiceFactory {
  private static class ResourceHolder {
    public static DBAccessService INSTANCE = new DBAccessService();
  }
  public static DBAccessService getResource() {
    return ResourceHolder.INSTANCE;
  }
}

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

По поводу нестабильного вопроса они говорят:

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

Другие советы

Вам необходимо объявить INSTANCE как volatile чтобы это работало:

private static volatile DBAccessService INSTANCE;

Обратите внимание, что он работает только с Java 5 и более поздних версий.Видеть Декларация «Двойная проверка блокировки нарушена».

В этой статье утверждается, что «двойное ведение журнала» не является проблемой, если вы используете отдельный класс Singleton:

public class DBAccessHelperSingleton {
    public static DBAccessHelper instance = new DBAccessHelper(); 
} 

Он имеет то же преимущество:Поле не создается до первого обращения к нему.

Как говорилось ранее, если вы хотите сохранить то, что у вас есть, volatile отсутствует, если вы ориентируетесь только на JDK >= 5.

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

public class DBAccessService() {
     public static DBAccessService INSTANCE = new DBAccessService();
}

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

Единственное, что volatile ключевое слово для ЭКЗЕМПЛЯРА.В противном случае Java может кэшировать его.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top