2015-11-21 8 views
2

Я просмотрел некоторые вопросы об этом фрагменте на StackOverflow, но ни один из них не упоминает проблему, о которой я узнал.Является ли код потокобезопасным?

Вот код:

@immutable // This is not a standard annotation .Only for Showing that behavior of Class 
class OneValueCached{ 
    private final BigInteger lastNumber; 
    private final BigInteger[] lastFactors; 
    public OneValueCached(BigInteger i,BigInteger[] factors){ 
     lastNumber=i; 
     lastFactors=Arrays.copyOf(factors, factors.length); 
    } 

    public BigInteger[] getFactors(BigInteger i){ 
     if(lastNumber==null || !lastNumber.equals(i)) // ---> line 2 
      return null; 
     else 
      return Arrays.copyOf(lastFactors, lastFactors.length); // ---> line 3 
    } 
} 

@threadSafe // This is not a standard annotation .Only for Showing that behavior of Class 
public class VolatileCachedFactorizer implements Servlet{ 
    private volatile OneValueCached cache=new OneValueCached(null, null); 

    public void service(ServletRequest req, ServletResponce resp){ 
     BigInteger i= extractFromRequest(req); 
     BigInteger[] factors=cache.getFactors(i); // ---> line 1 
     if(factors==null){ 
      factors=factor(i); 
      cache=new OneValueCached(i, factors); // ---> line 4 
     } 

     encodeIntoResponse(resp,factors); 
    } 
} 

Представьте,

Поток А приходит к линии 1, и вызывает cache.getFators(BigInteger i), и он приходит к строке 2, оператор условия возврата ложной.

Затем резьба В поступает в линию 1, а также вызывает cache.getFators(BigInteger i), когда дело доходит до строки 2, оператор условия возвращает true. Так что Thread B продолжается и переходит к строке 4, изменяет переменную cache на новую.

Резьба A продолжается и возвращается к строке 3, возвращает НЕПРЕРЫВНЫЙ результат!

Итак, что случилось? Является ли этот код потокобезопасным? (Согласно книге Java Concurrency на практике, да, это потокобезопасно)

обновление:

Я имею в виду, что, когда Thread B изменяет значение cache на новую нить может по-прежнему возвращать копию lastFactors предыдущего объекта. Я прав?

+2

Похоже, худшее поведение состоит в том, что он вычисляет два раза, но расчет является идемпотентным. – chrylis

+1

* Операции, связанные с кешем, не могут мешать друг другу, потому что OneValueCacheis неизменен и поле кэша доступно только один раз в каждом из соответствующих кодов кода. Эта комбинация неизменяемого объекта-держателя для нескольких переменных состояния, связанных с инвариантом, и изменчивая ссылка, используемая для обеспечения ее своевременной видимости, позволяет VolatileCachedFactorizer быть потокобезопасной, даже если она не имеет явной блокировки. * @hyd – user2916610

+2

Кстати, вы можете используйте 'factor.clone()' вместо 'Arrays.copyOf (факторы, факторы.длина) '. –

ответ

3

Да, этот код является потокобезопасным.

Поскольку OneValueCached неизменен, экземпляр его никогда не может вернуть неправильный результат, если он построен с правильными значениями. Либо он возвращает правильный результат, либо возвращает null.

При изменении значения кеширования переменная cache изменяется атомарно, поскольку она равна volatile. Возможно, в определенное время используются два экземпляра OneValueCached: вызывается метод для одного экземпляра, затем выполняется изменение потока, создается новый экземпляр и ссылка cache изменяется на то, чтобы указать на новый экземпляр, но это не влияет на другой поток и старый экземпляр, который не будет иметь права на сбор мусора до тех пор, пока не вернется вызов метода (переменная в методе выполнения сохраняет его в живых).

Furhtermore, нет никакой особой эффективности штраф для хранения ненужных экземпляров в cache, так как экземпляры должны быть созданы в любом случае, и на самом деле хранить ссылку в cache является несущественным с точки зрения производительности. Другой способ реализации кэширования (например, возможность хранения нескольких значений) мог бы повысить производительность, но делать это, как это все еще не хуже (разница в шаблоне использования памяти несущественна), чем вообще не кэширование, даже в худшем случае (кешированные значения никогда не использовались).

+0

Этот ответ должен начинаться с «Да, этот код является потокобезопасным» –

+1

@ user2916610, это метод оптимизации, называемый «memoization». Это googlable –

Смежные вопросы