在下面的代码(从复制的Java并发实践第2章,第2.5节,清单2.8):
@ThreadSafe
public class CachedFactorizer implements Servlet {
@GuardedBy("this") private BigInteger lastNumber;
@GuardedBy("this") private BigInteger[] lastFactors;
@GuardedBy("this") private long hits;
@GuardedBy("this") private long cacheHits;
public synchronized long getHits() { return hits; }
public synchronized double getCacheHitRatio() {
return (double) cacheHits / (double) hits;
}
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = null;
synchronized (this) {
++hits;
if (i.equals(lastNumber)) {
++cacheHits;
factors = lastFactors.clone(); // questionable line here
}
}
if (factors == null) {
factors = factor(i);
synchronized (this) {
lastNumber = i;
lastFactors = factors.clone(); // and here
}
}
encodeIntoResponse(resp, factors);
}
}
为什么factors
, lastFactors
阵列被克隆? 不能把它简单地写成factors = lastFactors;
和lastFactors = factors;
? 仅仅因为factors
是一个局部变量,然后将它传递给encodeIntoResponse
,它可以修改?
希望这个问题是清楚的。 谢谢。
这就是所谓的防御性复制。 数组是对象的任何其他,所以
factors = lastFactors
将assing lastFactos的因素,并且反之亦然的参考。 所以,任何人都可以覆盖你的控制范围之外的状态。 举个例子:
private void filterAndRemove(BigInteger[] arr);
private void encodeIntoResponse(..., BigInteger[] factors) {
filterAndRemove(factors);
}
随着我们的理论分配filterAndRemove也将影响到原来的lastFactorials。
如果你改变factors = lastFactors.clone();
到factors = lastFactors;
,两个factors
和lastFactors
指向同一个对象, factors
不再是局部变量,它成为一个共享的可变状态 。
试想一下,有三个请求,要求A,B,C的请求A发送,B是10,但要求C发送的号码是20。事情都可能出错,如果下面的执行顺序发生,你改变的数量factors = lastFactors.clone();
到factors = lastFactors;
。
- 小服务程序服务器接收到请求A,整个
service
执行方法,现在lastNumber
是10
, lastFactors
是[1, 2, 5, 10]
- 小服务程序服务器接收包括请求B和C,要求B在第一处理,但是离开第一后
synchronized
块( 现在为请求B, factors
是[1, 2, 5, 10]
这是正确的 ),请求C被处理。 - 对于请求C,整个
service
执行方法,其更改lastFactors
从[1, 2, 5, 10]
到[1, 2, 4, 5, 10, 20]
因为这两个factors
lastFactors
指向同一个对象, factors
现在[1, 2, 4, 5, 10, 20]
太。 请求B的响应被认为是[1, 2, 5, 10]
但是[1, 2, 4, 5, 10, 20]
现在 。
回答基本猜:你需要克隆如果你正计划修改的对象,你不想修改原单的对象,你的情况factors = lastFactors.clone();
这样做是因为你donnot要lastFactors
来代替你克隆,并将其发送到修改encodeIntoResponse(resp, factors);
其可以包含代码进行修改。
克隆阵列的唯一原因是阻止(在这种情况下并发)的数组元素的修改。 然而,这并不在这种情况下看可以假设没有其它方法修改由引用的数组lastFactors
这是有意义的给出的例子。 存储在阵列factors
和lastFactors
都创建并在一个完整的状态恢复factor
,他们引用synchronized块,这将导致他们被安全地发布内部分配。
除非encodeIntoResponse
修改它的factors
参数的元素,它看起来像我的电话clone
是不必要的。
我同意书的那部分本来可以更好地被书的作者解释。
这是事实,为了正确实现线程安全,必须同步读取和写入使用相同的锁定操作; 在上面的代码中,为了最小化同步的量,作者决定执行encodeIntoResponse(...)
不同步:由于encodeIntoResponse(...)
方法读出由所引用的数组的内容factors
然后笔者克隆到一个新的数组。
注:虽然这是事实, factors
是一个局部变量,在一般情况下,它仍然需要,因为在同一个阵列被同步读取和不同步码被克隆,如果我们通过引用(不克隆),它可能发生在lastFactors
和encodeIntoResponse(...)
然而,由于正确的问题,并通过在响应@大卫·哈克尼斯指出的@khachik,在这种特殊情况下的clone
调用是不必要的,因为lastFactors
安全地发布,并且它出版后不会被修改。
文章来源: “Java concurrency in practice” - cached thread-safe number factorizer (Listing 2.8)