我偶然发现了以下一段代码:
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
真的有必要吗?当我确定只能在这种方法中访问该集合时?
答案 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检查密钥。如果它不存在,请输入同步块并再次检查该键。这保证了每次都不进入同步块就不存在密钥。
设置修改通常也应该在同步块中进行。