使用ConcurrentHashMap(没有volatile关键字)实现参数化线程安全的lazy-init缓存

时间:2015-08-11 14:29:18

标签: java multithreading concurrency lazy-initialization

单一检查惯用法可用于实现线程安全延迟初始化(与双重检查相比)可能的缺点,即多个并发内容浪费一些计算时间。它是

单一检查成语

private volatile FieldType field;
FieldType getField() {
  FieldType result = field;
  if (result == null) {
    field = result = computeFieldValue();
  }
  return result;
}

在这里,我们需要volatile field来避免将部分初始化的对象传递给另一个线程,即 - 赋值(write)隐式执行必要的同步。

我想实现一个参数化延迟初始化缓存,它基本上由Map<Integer, Object>表示,其中每个元素都是使用惰性初始化创建的。

我的问题是:是否足以使用ConcurrentHashMap来避免部分初始化的问题。也就是说,在这种情况下,使用单一检查习语的lazy-init缓存的线程安全实现可以由

给出
private final ConcurrentHashMap<Integer, ItemType> items = new ConcurrentHashMap<Integer, ItemType>();

ItemType getItem(Integer index) {
  ItemType result = items.get(index);
  if (result == null) {
    result = computeItemValue(index);
    items.put(index, result);
  }
  return result;
}

换句话说:我假设&#39; items.put(索引,结果)&#39;执行必要的同步(因为它是写入)。请注意,问题可能是双重的:首先我想知道这是否适用于(a)当前的JVM实现,第二(更重要的是)我想知道ConcurrentHashMap是否保证(给定文档/合同)。 / p>

注意:这里我假设computeItemValue生成一个不可变对象,并在单一检查习语的意义上保证线程安全(也就是说,一旦完成对象构造,返回的对象对所有线程的行为都相同)。我认为这是J. Bloch的书中的第71项。

2 个答案:

答案 0 :(得分:3)

在java 8中,您可以使用computeIfAbsent来避免重复初始化的可能性:

private final ConcurrentHashMap<Integer, ItemType> items = new ConcurrentHashMap<Integer, ItemType>();

ItemType getItem(Integer index) {
  return items.computeIfAbsent(index, this::computeItemValue);
}

答案 1 :(得分:1)

  

是否足以使用ConcurrentHashMap来避免问题   部分初始化。

是的,在从CHM读取的对象相对于被写入的顺序之前有一个明确的发生。

所以,你覆盖了能见度,但还没有触及原子性。原子性有两个组成部分

  • 记忆化
  • 如果不存在则

你也没有实现。也就是说,对于memoization,您可以创建多个对象(实际上不是lazy-init)。对于put,如果不存在,CHM可以接收多个值,因为您没有调用putIfAbsent

根据您的上次更新,如果它们将是同一个对象并且是不可变的,那么尽管有额外的构造,您应该没问题。