Почему код из Java Concurrency in Practice считается безопасным

читаю книгу Java Concurrency in Practice и один пример "безопасного" кода из 3 главы насторожил меня. В книге сказано, что использование volatile-переменных в связке с неизменяемыми объектами могут обеспечить слабую форму атомарности и дан пример с факторизацией числа. Дан класс-холдер, который содержит число и его факторы, должен служить "кэшем".

@Immutable
class OneValueCache {
     private final BigInteger lastNumber;
     private final BigInteger[] lastFactors;
     public OneValueCache(BigInteger i, BigInteger[] factors) {
         lastNumber = i;
         lastFactors = Arrays.copyOf(factors, factors.length);
     }
     public BigInteger[] getFactors(BigInteger i) {
         if (lastNumber == null || !lastNumber.equals(i))
             return null;
         else
             return Arrays.copyOf(lastFactors, lastFactors.length);
     }
}

Также дан сервлет, который на вход получает число и должен отдать факторы этого числа, и если на входе сервлет два раза подряд получает одно и тоже число, то в первый раз он кэширует само число и его факторы, а во второй - просто достает из кэша. Код ниже.

@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
     private volatile OneValueCache cache = new OneValueCache(null, null);
     public void service(ServletRequest req, ServletResponse resp) {
         BigInteger i = extractFromRequest(req);
         BigInteger[] factors = cache.getFactors(i);
         if (factors == null) {
             factors = factor(i);
             cache = new OneValueCache(i, factors);
         }
         encodeIntoResponse(resp, factors);
     }
}

Дело в том, что я не могу понять, почему такой код считается безопасным? Допустим поток А принимает число 8, записывает в конструкторе класса OneValueCache lastNumber = 8; и зависает(условно). В то же время, поток В тоже получает число 8, пытается получить факторы числа 8 в строке BigInteger[] factors = cache.getFactors(i) класса VolatileCachedFactorizer и, выполняя этот метод, видит, что условие if (lastNumber == null || !lastNumber.equals(i)) не соблюдается и поэтому возвращает return Arrays.copyOf(lastFactors, lastFactors.length); Но поток А все еще висит и он не успел записать факторы числа 8, поэтому lastFactors все еще null, следовательно вызов Arrays.copyOf(lastFactors, lastFactors.length) привет к НПЕ. Поправьте меня, пожалуйста, где я ошибся?


Ответы (0 шт):