Может ли этот класс быть потокобезопасной ленивой загрузкой или он так же опасен, как «блокировка с двойной проверкой»? - PullRequest
2 голосов
/ 09 июля 2020

Пример кода выглядит следующим образом:

public class Lazy<T> implements Supplier<T> {

    public Lazy(Supplier<T> supplier) {
        this.supplier = Objects.requireNonNull(supplier);
    }

    Supplier<T> supplier;

    T value;

    @Override
    public T get() {
        if (supplier != null) {
            synchronized (this) {
                if (supplier != null) {
                    value = supplier.get();
                    supplier = null;
                }
            }
        }
        return value;
    }
}

Меня беспокоит, что если «поставщик» является конструктором. «supplier = null» может быть выполнено до инициализации объекта. Может произойти ошибка, похожая на «двойная проверка блокировки нарушена».

«supplier.get () == null» может быть истинным в этом классе. Поэтому я не проверяю, равно ли значение null

Если это небезопасно для потока, следует ли мне добавить «volatile» перед полем «поставщик»? Если это потокобезопасный, почему?

Ответы [ 2 ]

3 голосов
/ 09 июля 2020

Это немного сложно, но прочтите это . Короче, без volatile, это не работает. Вся конструкция может быть значительно упрощена:

public class Lazy<T> implements Supplier<T> {
    
     private final Supplier<T> supplier;

     volatile boolean computed = false;

     T value;

     public Lazy(Supplier<T> supplier) {
          this.supplier = Objects.requireNonNull(supplier);
     }

     @Override
     public T get() {
          if (!computed) {
                synchronized (this) {
                   if (!computed) {
                        value = supplier.get();
                        computed = true;
                   }
                }
           }

        return value;
     }

}
0 голосов
/ 09 июля 2020

Проблема в том, что теоретически у вас нет контроля над тем, что вернет поставщик .get (). Это может быть null, это может быть каждый раз другое значение и т. Д. c. По этой причине я утверждаю, что этот код не является потокобезопасным. Также обратите внимание, что «поставщик» будет иметь значение NULL только в том случае, если они это сделают:

new Lazy(null)

в противном случае он никогда не будет равен NULL. В этом случае вы также можете создать исключение в конструкторе

    public Lazy(Supplier<T> supplier) {
        if (supplier == null) {
            throw new IllegalArgumentException("'supplier' must not be null");
        }
        this.supplier = supplier;
    }

Я не уверен, чего вы здесь пытаетесь достичь. Если вы хотите лениво инициализировать "значение" только один раз, вы можете сделать что-то вроде:

if (value == null) {
    synchronize (this) {
        // test again to ensure no other thread initialized as we acquired monitor
        if (value == null) {
            value = supplier.get();
        }
    }
}

return value;
...