LazyReference с двойной проверкой блокировки и обработкой нуля - PullRequest
0 голосов
/ 22 июня 2011

Я использую LazyReference класс в течение нескольких лет (конечно, не регулярно, но иногда это очень полезно). Класс можно увидеть здесь . Кредиты поступают к Робби Ванбрабанту (автору класса) и Джошуа Блоху с его знаменитым «Effective Java 2nd edt». (оригинальный код).

Класс работает правильно (в Java 5+), но есть одна небольшая потенциальная проблема. Если instanceProvider возвращает null (что не обязательно в соответствии с контрактом Guice Provider.get(), но ...), то при каждом выполнении метода LazyReference.get() LOCK будет удерживаться и instanceProvider.get будет вызываться снова и снова. Это выглядит хорошим наказанием для тех, кто нарушает контракты (хе-хе), но что, если действительно нужно лениво инициализировать поле с возможностью установить значение null?

Я немного изменил LazyReference:

public class LazyReference<T> {

  private final Object LOCK = new Object();

  private volatile T instance;

  private volatile boolean isNull;

  private final Provider<T> instanceProvider;

  private LazyReference(Provider<T> instanceProvider) {
    this.instanceProvider = instanceProvider;
  }

  public T get() {
    T result = instance;
    if (result == null && !isNull) {
      synchronized (LOCK) {
        result = instance;
        if (result == null && !isNull) {
          instance = result = instanceProvider.get();
          isNull = (result == null);
        }
      }
    }
    return result;
  }
}

ИМХО, это должно работать просто отлично (если у вас есть другое мнение, пожалуйста, оставьте свои комментарии и критику). Но мне интересно, что произойдет, если я удалю модификатор volatile из логического значения isNull (конечно, оставив его на instance)? Будет ли он работать правильно?

Ответы [ 2 ]

3 голосов
/ 22 июня 2011

Приведенный выше код имеет условие состязания: для экземпляра может быть задано «реальное» значение null из результата instanceProvider.get () до того, как isNull было установлено.

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

2 голосов
/ 22 июня 2011

Как отметил Нил Коффи, этот код содержит условие гонки, но его можно легко исправить следующим образом (обратите внимание, что instance не обязательно должно быть volatile):

public class LazyReference<T> {     
  private T instance;
  private volatile boolean initialized;
  ...
  public T get() {
    if (!initialized) {
      synchronized (LOCK) {
        if (!initialized) {
          instance = instanceProvider.get();
          initialized = true;
        }
      }
    }
    return instance;
  }
}
...