Java Concurrency in Practice:清单3.12和3.13

时间:2015-09-11 20:27:08

标签: java multithreading concurrency

我们有这些对象:

//清单3.12

@Immutable
class OneValueCache {
    private final BigInteger lastNumber;
    private final BigInteger[] lastFactors;

    public OneValueCache(BigInteger i, BigInteger[] factors) {
        lastNumber = i;
        lastFactors = Arrays.copyOf(factors, factors.length);
    }

    public BigInteger[] getFactors(BigInteger i) {
        if (lastNumber == null || !lastNumber.equals(i)) {
            return null;
        } else {
            return Arrays.copyOf(lastFactors, lastFactors.length);
        }
   }
}

//清单3.13

@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
    private volatile OneValueCache cache =
            new OneValueCache(null, null);

    public void service(ServletRequest req, ServletResponse resp) {  
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = cache.getFactors(i);

        if (factors == null) {
            factors = factor(i);
            cache = new OneValueCache(i, factors);
        }
        encodeIntoResponse(resp, factors);
    }
}

在书中,据说VolatileCachedFactorizer是线程保存。为什么呢?

例如:

  1. 线程A读取cache.getFactors(i);
  2. 然后线程B读取cache.getFactors(i);
  3. 然后线程B写入缓存。
  4. 然后线程A写入缓存。
  5. 这个流程可以被视为线程保存吗?我什么不明白?

2 个答案:

答案 0 :(得分:3)

简答:

线程安全并不是绝对的。您必须确定所需的行为,然后询问实现是否为您提供存在多线程的行为。

更长的回答:

那么,这里的理想行为是什么?只是总是给出了正确的答案,还是如果两个线程连续要求它总是实现一次?

如果它是后者 - 也就是说,如果你真的想要保存每一点CPU - 那么你是对的,这不是线程安全的。两个请求可以同时进入(或者足够接近它)以获得相同数量N的因子,并且如果时间计算出来,则两个线程都可能最终计算该数字。

但是使用单值缓存,您已经遇到了重新计算已知事物的问题。例如,如果三个请求再次进入N,K和N,该怎么办?对K的请求会使N处的缓存无效,因此您必须重新计算它。

所以,这个缓存真的针对"条纹"进行了优化。具有相同价值,因此在该条纹中两次计算第一对(或甚至几个!)答案的成本可能是可接受的成本:作为回报,您获得的代码没有任何阻塞和漂亮简单易懂。

至关重要的是它永远不会给你错误的答案。也就是说,如果你同时要求N和K,K的响应永远不会给你N的答案。这个实现可以保证你的安全,所以我称之为线程安全。

答案 1 :(得分:0)

这是线程安全的。最终目标是确保service方法输出正确。 OneValueCache始终保持lastNumberlastFactors之间的正确关系。