我在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);
}
}
想象一下,
线程A到达第1行,并调用cache.getFators(BigInteger i)
,它来到第2行,条件语句返回 false 。
然后线程B到达第1行,并且还调用cache.getFators(BigInteger i)
,当它到达第2行时,条件语句返回 true 。因此,线程B继续,并且到第4行,将变量cache
更改为新的。
线程A继续,到第3行,返回错误的结果!
那有什么不对?这段代码是线程安全的吗? (根据 Java Concurrency in Practice 一书,是的,它是线程安全的)
更新
我正在考虑当线程B将cache
的值更改为新值时,线程仍然可以返回先前对象的lastFactors的副本。我是对的吗?
答案 0 :(得分:3)
是的,这段代码是线程安全的。
由于OneValueCached
是不可变的,因此它的实例永远不会返回错误的结果,假设它是用正确的值构造的。要么返回正确的结果,要么返回null
。
更改缓存值后,cache
变量会自动更改,因为它是volatile
。可能是在某个时间使用了两个OneValueCached
个实例:调用一个实例的方法,然后执行线程更改,创建一个新实例并更改cache
引用以指向一个新实例,但它不会影响其他线程和旧实例,它不会有资格进行垃圾收集,直到方法调用返回(执行方法中的this
变量使其保持活动状态)。 / p>
此外,在cache
中存储不必要的实例没有特别的效率损失,因为无论如何都需要创建实例,并且从性能的角度来看,实际存储cache
中的引用是无关紧要的。实现缓存的不同方式(例如能够存储多个值)可以进一步提高性能,但这样做仍然没有比没有缓存更糟糕(内存使用模式的差异无关紧要),即使在最坏的情况下(缓存的值从未使用过。)