【问题标题】:volatile + immutable holder object = thread safe?volatile + 不可变持有者对象 = 线程安全?
【发布时间】:2014-07-23 12:44:09
【问题描述】:

我有一个来自“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 类是不可变的。但是我们可以改变变量缓存的引用。

但我不明白为什么类 VolatileCachedFactorizer 是线程安全的。

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

说明代码不是线程安全的。

谁能告诉我为什么这段代码被认为是线程安全的以及为什么我错了?

【问题讨论】:

  • 请注意,这是一个玩具箱。大多数线程安全问题要复杂得多。
  • 我也很困惑.. xD 但后来我意识到任务只存储最后一个因素。就那么简单。如果同时计算两次或其他什么都没关系......
  • 我不明白,为什么我无法解析因子(i)是java数学函数???

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


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    线程安全操作有两个属性

    1. 可见性
    2. 原子性

    要实现完全线程安全的操作,它需要同时满足这两个要求。

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

    谁能告诉我为什么这段代码被认为是线程安全的以及为什么我错了?

    你在这里的倾向是正确的,你对这个班级的安全性的质疑是合法的。

    【讨论】:

      【解决方案3】:

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

      但是,这不会阻止线程同时执行相同的分解工作。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-06-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多