7

在以下代码中(复制自 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;andlastFactors = factors;吗?仅仅因为factors是一个局部变量然后它被传递给encodeIntoResponse,它可以修改它吗?

希望问题很清楚。谢谢。

4

5 回答 5

2

这称为防御性复制。数组和其他对象一样是对象,所以

 factors = lastFactors

会将 lastFactos 的引用分配给因子,反之亦然。所以任何人都可以在你的控制之外覆盖你的状态。举个例子:

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

使用我们的理论赋值 filterAndRemove 也会影响原来的 lastFactorials。

于 2012-10-05T11:09:52.757 回答
0

从基础猜到的答案:如果您打算修改对象,并且您不想修改原始对象,则需要克隆,在您的情况下factors = lastFactors.clone();,因为您不想lastFactors被修改而是克隆它并将其发送到encodeIntoResponse(resp, factors);哪个可能包含修改它的代码。

于 2012-08-20T08:31:18.970 回答
0

克隆数组的唯一原因是阻止(在这种情况下并发)修改数组元素。但是,在这种情况下,这看起来是不可能的,假设没有其他方法修改引用的数组,lastFactors这在示例中是有意义的。存储在factors和中的数组lastFactors都是由 以完整状态创建和返回的factor,并且它们的引用在同步块中分配,这将导致它们被安全地发布。

除非encodeIntoResponse修改其factors参数的元素,否则在我看来,调用clone是不必要的。

于 2012-10-23T15:04:27.007 回答
0

我同意本书的作者可以更好地解释本书的这一部分。

确实,为了正确实现线程安全,您必须使用同一个锁来同步读写操作在上面的代码中,为了尽量减少同步量,作者决定执行无同步:由于该方法读取了所引用的数组的内容,因此作者将其克隆到一个新的数组中。encodeIntoResponse(...)encodeIntoResponse(...)factors

注意:虽然它确实factors是一个局部变量,但一般来说,它仍然需要被克隆,因为同步和非同步代码读取相同的数组,如果我们将引用(不克隆)传递给lastFactorsand ,可能会发生这种情况encodeIntoResponse(...)

但是,正如@khachik 在问题中和@david-harkness 在回复中正确指出的那样,在这种特定情况下,clone调用是不必要的,因为lastFactors它是安全发布的,并且在发布后不会被修改。

于 2016-10-19T07:47:04.207 回答
0

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

假设有三个请求,请求 A、B、C。请求 A 和 B 发送的数量是 10,但请求 C 发送的数量是 20。如果发生以下执行顺序并且您更改factors = lastFactors.clone();为 ,则可能会出错factors = lastFactors;

  1. servlet服务器接收到请求A,整个service方法被执行,now lastNumberis 10, lastFactorsis [1, 2, 5, 10]
  2. servlet 服务器接收到请求 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]现在是。
于 2019-04-09T13:39:17.693 回答