我在 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的副本。我说得对吗?
最佳答案
是的,这段代码是线程安全的。
由于 OneValueCached
是不可变的,因此它的实例永远不会返回错误的结果(假设它是使用正确的值构造的)。它要么返回正确的结果,要么返回null
。
当缓存值更改时,cache
变量会自动更改,因为它是 volatile
。可能在某一时刻使用了两个 OneValueCached
实例:调用一个实例的方法,然后执行线程更改,创建一个新实例并更改 cache
引用指向一个新实例,但它不会影响其他线程和旧实例,在方法调用返回之前,旧实例不符合垃圾回收条件(执行方法中的 this
变量保持它还活着)。
此外,将不必要的实例存储在缓存
中不会带来特殊的效率损失,因为无论如何都需要创建实例,并且实际上将引用存储在缓存
中是无关紧要的从性能的角度来看。实现缓存的不同方式(例如能够存储多个值)可以进一步提高性能,但这样做仍然不会比根本不缓存更糟糕(内存使用模式的差异无关紧要),即使在最坏的情况下也是如此(从未使用过缓存值)。
关于java - 代码是线程安全的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33844905/