“Java并发实践” - 缓存线程安全号码factorizer(清单2.8)(“Java conc

2019-09-21 09:44发布

在下面的代码(从复制的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);
    }
}

为什么factorslastFactors阵列被克隆? 不能把它简单地写成factors = lastFactors;lastFactors = factors; ? 仅仅因为factors是一个局部变量,然后将它传递给encodeIntoResponse ,它可以修改?

希望这个问题是清楚的。 谢谢。

Answer 1:

这就是所谓的防御性复制。 数组是对象的任何其他,所以

 factors = lastFactors

将assing lastFactos的因素,并且反之亦然的参考。 所以,任何人都可以覆盖你的控制范围之外的状态。 举个例子:

private void filterAndRemove(BigInteger[] arr);
private void encodeIntoResponse(..., BigInteger[] factors) {
   filterAndRemove(factors);
}

随着我们的理论分配filterAndRemove也将影响到原来的lastFactorials。



Answer 2:

如果你改变factors = lastFactors.clone();factors = lastFactors; ,两个factorslastFactors指向同一个对象, factors 不再是局部变量,它成为一个共享的可变状态

试想一下,有三个请求,要求A,B,C的请求A发送,B是10,但要求C发送的号码是20。事情都可能出错,如果下面的执行顺序发生,你改变的数量factors = lastFactors.clone();factors = lastFactors;

  1. 小服务程序服务器接收到请求A,整个service执行方法,现在lastNumber10lastFactors[1, 2, 5, 10]
  2. 小服务程序服务器接收包括请求B和C,要求B在第一处理,但是离开第一后synchronized块( 现在为请求B, factors[1, 2, 5, 10]这是正确的 ),请求C被处理。
  3. 对于请求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]现在


Answer 3:

回答基本猜:你需要克隆如果你正计划修改的对象,你不想修改原单的对象,你的情况factors = lastFactors.clone(); 这样做是因为你donnot要lastFactors来代替你克隆,并将其发送到修改encodeIntoResponse(resp, factors); 其可以包含代码进行修改。



Answer 4:

克隆阵列的唯一原因是阻止(在这种情况下并发)的数组元素的修改。 然而,这并不在这种情况下看可以假设没有其它方法修改由引用的数组lastFactors这是有意义的给出的例子。 存储在阵列factorslastFactors都创建并在一个完整的状态恢复factor ,他们引用synchronized块,这将导致他们被安全地发布内部分配。

除非encodeIntoResponse修改它的factors参数的元素,它看起来像我的电话clone是不必要的。



Answer 5:

我同意书的那部分本来可以更好地被书的作者解释。

这是事实,为了正确实现线程安全,必须同步读取写入使用相同的锁定操作; 在上面的代码中,为了最小化同步的量,作者决定执行encodeIntoResponse(...)不同步:由于encodeIntoResponse(...)方法读出由所引用的数组的内容factors然后笔者克隆到一个新的数组。

注:虽然这是事实, factors是一个局部变量,在一般情况下,它仍然需要,因为在同一个阵列被同步读取和不同步码被克隆,如果我们通过引用(不克隆),它可能发生在lastFactorsencodeIntoResponse(...)

然而,由于正确的问题,并通过在响应@大卫·哈克尼斯指出的@khachik,在这种特殊情况下的clone调用是不必要的,因为lastFactors安全地发布,并且它出版后不会被修改。



文章来源: “Java concurrency in practice” - cached thread-safe number factorizer (Listing 2.8)