volatile + immutable holder object =线程安全吗?

时间:2014-07-23 12:44:09

标签: java thread-safety immutability volatile java-memory-model

我从“java concurrency pratique”一书中得到了一个例子,他说易失性和不可变的持有者对象提供了线程安全性。但我不明白这本书给出的例子。

代码如下:

public class VolatileCachedFactorizer extends GenericServlet 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);  //----------> thread A
        cache = new OneValueCache(i, factors);  //---------> thread B
    }
    encodeIntoResponse(resp, factors);
  }   
 }

public class OneValueCache {

  private final BigInteger lastNum;
  private final BigInteger[] lastFactors;

  public OneValueCache(BigInteger i, BigInteger[] lastFactors){
    this.lastNum = i;
    this.lastFactors = lastFactors;
  }

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

}

我理解

  1. 关键字volatile确保文件缓存对所有线程都可见。

  2. OneValueCache类是不可变的。但我们可以更改变量缓存的引用。

  3. 但我无法理解为什么类VolatileCachedFactorizer是线程安全的。

    对于两个线程(线程A和线程B),如果线程A和线程B同时到达factors == null,则两个线程A和B都将尝试创建OneValueCache。然后线程A到达factors = factor(i),而threadB同时到达cache = new OneValueCache(i, factors)。然后线程A将创建一个OneValueCache,它将覆盖threadB创建的值(OneValueChange是不可变的,但可以更改对变量缓存的引用)。

    它表明代码不是线程安全的。

    有人能告诉我为什么这段代码被认为是线程安全的,为什么我错了?

4 个答案:

答案 0 :(得分:4)

因此,两个线程计算因子(一个用于67,另一个用于89),然后将其结果存储到缓存变量中。设置变量的最后一个线程获胜。假设第一个线程是将其因子存储在缓存中的最后一个线程。因此,缓存包含67的因子。

如果后续执行要求67的因子,它将从缓存中获取它们(因为缓存不为空,并包含67的因子)。如果它要求另一个数字的因子,它将不会从缓存中获取它们,因此将计算因子,并将它们存储在缓存中,希望以下请求将要求相同数字的因子。

没有什么能保证两个线程不会计算相同数字的因子。此代码提供的唯一保证是,如果缓存当前包含所请求数字的因子,则将返回这些缓存因子(而不是其他数字的因子,或数据争用导致的不一致数据)

答案 1 :(得分:3)

线程安全操作有两个属性

  1. 可见性
  2. 原子性
  3. 要完成一个完全线程安全的操作,它需要满足这两个要求。

    在您的示例中,任何数据争用(即可见性)(1)都是安全的,但不是原子(2)。机会是,作者想要说明上面的代码是安全的发布,而忽略了指出(或者你没有读过)它不是原子的。

      

    有人能告诉我为什么这段代码被认为是线程安全的,为什么我错了?

    你在这里的倾向是正确的,你质疑这门课程的安全性是合法的。

答案 2 :(得分:1)

在某种意义上,只有当OneValueCache处于有效状态时,缓存值才对其他线程可见,这是线程安全的。不可变类保证所有值都有效,因为每次值更改时都必须实例化类(即,您不能通过逐个更改字段来更新已发布的实例)。

但是,这并不妨碍线程同时执行相同的分解工作。

答案 3 :(得分:0)

据我所知,这种情况下的并发问题是缓存中保存的因子不是从缓存中保存的相应lastnumber计算出来的。程序在计算了使线程安全的因素之后,每次都会初始化一个新的缓存。