从缓存中获取的Set中的线程安全性

时间:2018-03-19 15:21:33

标签: java multithreading thread-safety

我偶然发现了以下一段代码:

public static final Map<String, Set<String>> fooCacheMap = new ConcurrentHashMap<>();

从休息控制器方法访问此缓存:

public void fooMethod(String fooId) {
    Set<String> fooSet = cacheMap.computeIfAbsent(fooId, k -> new ConcurrentSet<>());
    //operations with fooSet
}

ConcurrentSet真的有必要吗?当我确定只能在这种方法中访问该集合时?

3 个答案:

答案 0 :(得分:2)

当您在控制器中使用它时,多个线程可以同时调用您的方法(例如,多个并行请求可以调用您的方法)

由于此方法看起来不像是以任何方式同步,因此这里可能需要ConcurrentSet。

答案 1 :(得分:0)

  

ConcurrentSet真的有必要吗?

可能,可能不是。我们不知道如何使用此代码。

但是,假设它以多线程方式使用(具体来说:两个线程可以同时调用fooMethod),是的。

ConcurrentHashMap中的原子性仅在computeIfAbsent的每次调用时得到保证。完成后,锁被释放,其他线程可以调用该方法。因此,对返回值的访问不是原子的,因此您可以在访问该值时获得线程推断。

就问题而言,我需要`ConcurrentSet&#34 ;?否:你可以这样做,以便访问集合是原子的:

cacheMap.compute(fooId, (k, fooSet) -> {
  if (fooSet == null) fooSet = new HashSet<>();
  // Operations with fooSet
  return v;
});

答案 2 :(得分:0)

使用并发映射不能保证线程安全。需要在同步块中执行对Map的添加,以确保两个线程不会尝试将相同的键添加到映射中。因此,并不真正需要并发映射,尤其是因为Map本身是静态的和最终的。此外,如果代码修改了Map内部的Set,这看起来可能也需要同步。

正确的方法是Map检查密钥。如果它不存在,请输入同步块并再次检查该键。这保证了每次都不进入同步块就不存在密钥。

设置修改通常也应该在同步块中进行。