I had an example from the book 'java concurrency pratique', who says that volatile and immutable holder object gives thread safety. But I do not understand the example given by the book.
The code is as follows:
public class VolatileCachedFactorizer extends GenericServlet 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); //----------> thread A
cache = new OneValueCache(i, factors); //---------> thread B
}
encodeIntoResponse(resp, factors);
}
}
public class OneValueCache {
private final BigInteger lastNum;
private final BigInteger[] lastFactors;
public OneValueCache(BigInteger i, BigInteger[] lastFactors){
this.lastNum = i;
this.lastFactors = lastFactors;
}
public BigInteger[] getFactors(BigInteger i){
if(lastNum == null || !lastNum.equals(i))
return null;
else
return Arrays.copyOf(lastFactors, lastFactors.length);
}
}
I understand that
The key word volatile assure that the filed cache is visible to all threads.
The class OneValueCache is immutable. But we can change the reference of the variable cache.
But I cannot understand why the class VolatileCachedFactorizer is thread safe.
For two threads (Thread A and Thread B), if thread A and thread B arrive at factors == null
at the same time, the two threads A and B will both try to create the OneValueCache. Then Thread A arrives at factors = factor(i)
while threadB arrives at cache = new OneValueCache(i, factors)
at the same time. Then the thread A will create a OneValueCache
which overwrites the value created by threadB (OneValueChange is immutable but the reference to the variable cache can be changed).
It shows that the code is not thread safe.
Could anyone tell me why this piece of code is considered to be thread safe and why I am wrong ?
So, two threads compute factors (one for 67, and the other for 89), and then store their result into the cache variable. The last thread that sets the variable wins. Let's say the first thread is the last one to store its factors in the cache. The cache thus contains the factors for 67.
If a subsequent execution asks for the factors of 67, it will get them from the cache (because the cache is not null, and contains the factors for 67). If it asks for the factors of another number, it won't get them from the cache, will thus compute the factors, and store them in the cache, hoping that the following requests will ask for the factors of the same number.
Nothing guarantees that two threads won't compute the factors from the same number. The only guarantee that this code offers is that, if the cache currently contains the factors for the requested number, these cached factors will be returned (and not factors for another number, or inconsistent data cause by a data race)