我对在我们的一些遗留代码中看到的一种模式有点困惑。
Controller 使用 map 作为缓存,其方法应该是线程安全的,但我仍然不确定它确实是安全的。我们有一个映射,它在添加和检索期间正确同步,但是,在同步块(synchronized block)之外有一些逻辑,它会进行一些额外的过滤。 ( map 本身和列表永远不会在该方法之外访问,因此并发修改不是问题; map 拥有一些稳定的参数,这些参数基本上不会改变,但经常使用)。 p>
代码类似于以下示例:
public class FooBarController {
private final Map<String, List<FooBar>> fooBarMap =
new HashMap<String, List<FooBar>>();
public FooBar getFooBar(String key, String foo, String bar) {
List<FooBar> foobarList;
synchronized (fooBarMap) {
if (fooBarMap.get(key) == null) {
foobarList = queryDbByKey(key);
fooBarMap.put(key, foobarList);
} else {
foobarList = fooBarMap.get(key);
}
}
for(FooBar fooBar : foobarList) {
if(foo.equals(fooBar.getFoo()) && bar.equals(fooBar.getBar()))
return fooBar;
}
return null;
}
private List<FooBar> queryDbByKey(String key) {
// ... (simple Hibernate-query)
}
// ...
}
根据我对 JVM 内存模型的了解,这应该没问题,因为如果一个线程填充一个列表,另一个线程只能在适当同步的情况下从映射中检索它,从而确保列表的条目是可见的。 (将列表发生在获取之前)
但是,我们不断看到这样的情况:未找到预期在映射中的条目,再加上并发问题的典型臭名昭著的症状(例如,生产中的间歇性故障,我无法在我的开发环境中重现;不同的线程可以正确检索值等)
我想知道像这样迭代列表的元素是否是线程安全的?
最佳答案
您提供的代码在并发性方面是正确的。以下是保证:
- 由于映射对象上的同步,一次只有一个线程向映射添加值
- 线程添加的值对于进入同步块(synchronized block)的所有其他线程都可见
鉴于此,您可以确定迭代列表的所有线程都会看到相同的元素。您描述的问题确实很奇怪,但我怀疑它们与您提供的代码有关。
关于java - 迭代同步块(synchronized block)中检索的列表是线程安全的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35083000/