我对我在一些遗留代码中看到的一种模式感到有些困惑。
控制器使用一个映射作为缓存,其方法应该是线程安全的,但我仍然不确定它确实存在。我们有一个映射,它在添加和检索期间正确同步,但是,在synchronized块之外还有一些逻辑,它会进行一些额外的过滤。 (地图本身和列表永远不会在此方法之外访问,因此并发修改不是问题;地图包含一些稳定的参数,这些参数基本上不会更改,但经常使用)。
代码如下所示:
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内存模型的了解,这应该没问题,因为如果一个线程填充一个列表,另一个线程只能通过适当的同步从地图中检索它,确保列表的条目是可见。 (将列表发生 - 在获取之前)
但是,我们不断发现未找到预期在地图中的条目的情况,以及并发问题的典型臭名昭着的症状(例如生产中的间歇性故障,我无法在我的开发环境中重现;不同的线程可以正确检索值等。)
我想知道迭代List的元素是否是线程安全的?
答案 0 :(得分:1)
您提供的代码在并发性方面是正确的。以下是保证:
鉴于此,您可以确保迭代列表的所有线程都看到相同的元素。您描述的问题确实很奇怪,但我怀疑它们与您提供的代码有关。
答案 1 :(得分:0)
只有同步所有访问fooBarMap
时,它才可以是线程安全的。有点超出范围,但更安全的可能是使用ConcurrentHashmap。
有一篇很棒的文章介绍了如何同步哈希映射here。
答案 2 :(得分:0)
ConcurrentHashMap
。正如我从你的问题中所理解的那样。有一组永远不会改变的params
修正集。在这种情况下我喜欢的方式之一是:
予。在启动期间创建map cache
并仅保留其中的一个实例。
II。在应用程序的任何地方随时阅读map Instance
。
答案 3 :(得分:0)
在for循环中,您将返回对foobarList中fooBar对象的引用。
因此调用getFooBar()的方法可以通过这个fooBar引用对象访问Map。
尝试在从getFooBar()
返回之前克隆fooBar