迭代在同步块中检索的列表是否是线程安全的?

时间:2016-01-29 11:09:17

标签: java multithreading thread-safety

我对我在一些遗留代码中看到的一种模式感到有些困惑。

控制器使用一个映射作为缓存,其方法应该是线程安全的,但我仍然不确定它确实存在。我们有一个映射,它在添加和检索期间正确同步,但是,在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的元素是否是线程安全的?

4 个答案:

答案 0 :(得分:1)

您提供的代码在并发性方面是正确的。以下是保证:

  • 由于地图对象上的同步
  • ,一次只有一个线程将值添加到地图中
  • 线程添加的值对于进入synchronized块
  • 的所有其他线程都是可见的

鉴于此,您可以确保迭代列表的所有线程都看到相同的元素。您描述的问题确实很奇怪,但我怀疑它们与您提供的代码有关。

答案 1 :(得分:0)

只有同步所有访问fooBarMap时,它才可以是线程安全的。有点超出范围,但更安全的可能是使用ConcurrentHashmap

有一篇很棒的文章介绍了如何同步哈希映射here

答案 2 :(得分:0)

  1. 在这种情况下,最好使用ConcurrentHashMap
  2. 验证所有Update-Read是否有序。
  3. 正如我从你的问题中所理解的那样。有一组永远不会改变的params修正集。在这种情况下我喜欢的方式之一是:

    予。在启动期间创建map cache并仅保留其中的一个实例。

    II。在应用程序的任何地方随时阅读map Instance

答案 3 :(得分:0)

在for循环中,您将返回对foobarList中fooBar对象的引用。

因此调用getFooBar()的方法可以通过这个fooBar引用对象访问Map。

尝试在从getFooBar()

返回之前克隆fooBar