Java中使用volatile关键字与Java链表相关。

4

我的构造函数中添加到易失性链表中的项目可能对其他线程不可见,这是正确的吗?

class ProductPrice {
  private volatile LinkedList<QuantityPrice> quantityPriceList;
  public ProductPrice(List<QuantityPrice> quantityPriceListParam) {
      this.quantityPriceList = new LinkedList<QuantityPrice>();
      quantityPriceList.addAll(quantityPriceListParam);
  }
}

以下代码,在列表加载完成后再为 volatile 变量赋值,这样能解决问题吗?因为所有的 happen-before 操作也会被可见。
private volatile LinkedList<QuantityPrice> quantityPriceList;
public ProductPrice(List<QuantityPrice> quantityPriceListParam) {
    LinkedList<QuantityPrice> tempQuantityLinkedList = new LinkedList<QuantityPrice>();
    tempQuantityLinkedList.addAll(quantityPriceListParam);
    this.quantityPriceList = tempQuantityLinkedList;
}

在这种情况下,我只需要将变量声明为final,就可以获得相同的效果,即使所有项对其他线程可见。

private final LinkedList<QuantityPrice> quantityPriceList;
public ProductPrice(List<QuantityPrice> quantityPriceListParam) {
    LinkedList<QuantityPrice> tempQuantityLinkedList = new LinkedList<QuantityPrice>();
    tempQuantityLinkedList.addAll(quantityPriceListParam);
    this.quantityPriceList = tempQuantityLinkedList;
}
3个回答

2
如果LinkedList是在另一个线程中构建的,则需要读取volatile以产生读取障碍(假设未使用线程间安全的传递方式)。为了安全起见,需要最后执行写入以查看发生的所有写入。
您需要第一种组合来触发volatile读取,在复制列表之前。第二个则需要触发volatile写入最后执行。
假设您的QuantityPrice不会改变,这将起作用。
顺便说一句,使用ArrayList可能会更快。
另外,更快的方法是尝试找到避免创建对象的方法,但没有足够的代码可以解决这个问题。

你确定构造函数的语义允许所有添加操作对其他线程可见吗?你不知道这在JLS中是否有说明吗? - richs
据我所知,理论上的易失性写操作可能会与初始默认存储竞争。因此,调用线程可能会看到一个非空的ProductPrice实例,其中quantityPriceList为空。 - John Vint
@richs 一个volatile写操作对其他线程是可见的,但顺序很重要,读取操作也是如此。我已经更新了我的答案。 - Peter Lawrey

2
如果我在构造函数中添加到我的易失性链表中的项目可能对其他线程不可见,这是正确的吗?
如果列表非空,则所有对列表的写入都将可见。存在一种竞争条件,在该条件下默认写入(null)可能对看到非空ProductPrice的线程可见。如果该字段是final,则不是这样。
在这种情况下,我可以只使变量为final并获得所有项目对其他线程可见的相同效果吗?
是的,这是最好的解决方案。

竞争条件?这是否意味着链表的状态最终会被发布到所有CPU缓存中,但不能保证立即完成? - richs
我的意思是,如果在为它分配一个“new”变量之前查看列表,它显然为null。JMM允许其他线程看到构造完成后的null值。 - John Vint
如果该列表非空,则构造函数中添加到列表中的所有内容都将可见。 - John Vint
尽管列表是易变的,但我认为在构造之后,空值不会对其他线程可见。 - richs
我也是 :) 虽然程序员不能依赖这一事实,但这种情况不太可能发生。http://cs.oswego.edu/pipermail/concurrency-interest/2013-November/011951.html - John Vint

1
如果quantityPriceList变量确实可以是final的,那么最好的解决方案是将其声明为final,并通过使用接受集合的构造函数来填充它。
class ProductPrice {
  private final LinkedList<QuantityPrice> quantityPriceList;
  public ProductPrice(List<QuantityPrice> quantityPriceListParam) {
      this.quantityPriceList = new LinkedList<QuantityPrice>(quantityPriceListParam);
  }
}

你提供的第一个示例可能存在并发问题,具体取决于ProductPrice对象的可用性是如何发布的。这与现有的问题非常相似。
正如@PeterLawrey所指出的那样,在你确切知道要放入列表中的项目数量的情况下,使用ArrayList几乎肯定比LinkedList更好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接