简体   繁体   English

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

[英]Is iterating over a list retrieved in a synchronized block thread-safe?

I am a bit confused regarding one pattern I have seen in some legacy code of ours. 对于在我们的一些旧代码中看到的一种模式,我有些困惑。

The controller uses a map as a cache, with an approach that should be thread safe, however I am still not confident it indeed is. 控制器使用映射作为缓存,并应采用线程安全的方法,但是我仍然不确定确实如此。 We have a map, which is properly synchronized during addition and retrieval, however, there is a bit of logic outside of the synchronized block, that does some additional filtering. 我们有一个映射,该映射在添加和检索期间已正确同步,但是,在同步块之外还有一些逻辑,它会进行一些附加过滤。 ( the map itself and the lists are never accessed outside of this method , so concurrent modification is not an issue; the map holds some stable parameters, which basically never change, but are used often). 映射本身和列表永远不会在此方法之外访问 ,因此并发修改不是问题;映射拥有一些稳定的参数,这些参数基本上从不更改,但经常使用)。

The code looks like the following sample: 该代码类似于以下示例:

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) 
    } 

    // ... 
}

Based on what I know about the JVM memory model, this should be fine, since if one thread populates a list, another one can only retrieve it from the map with proper synchronization in place, ensuring that the entries of the list is visible. 根据我对JVM内存模型的了解,这应该很好,因为如果一个线程填充了一个列表,则另一个线程只能在适当的同步下从映射中检索它,从而确保列表的条目可见。 (putting the list happens-before getting it) (让列表获取之前发生

However, we keep seeing cases, where an entry expected to be in the map is not found, combined with the typical notorious symptoms of concurrency issues (eg intermittent failures in production, which I cannot reproduce in my development environment; different threads can properly retrieve the value etc.) 但是,我们不断看到没有在地图中找到预期条目的情况,以及并发问题的典型臭名昭著的症状(例如,生产中的间歇性故障,我无法在开发环境中重现;不同的线程可以正确检索值等)

I am wondering if iterating through the elements of the List like this is thread-safe? 我想知道像这样遍历List的元素是否是线程安全的?

The code you provided is correct in terms of concurrency. 您提供的代码在并发方面是正确的。 Here are the guarantees: 以下是保证:

  • only one thread at a time adds values to map, because of synchronization on map object 一次只有一个线程会向地图添加值,因为地图对象上的同步
  • values added by thread become visible for all other threads, that enter synchronized block 线程添加的值对于进入同步块的所有其他线程变为可见

Given that, you can be sure that all threads that iterate a list see the same elements. 鉴于此,您可以确保迭代列表的所有线程看到相同的元素。 The issues you described are indeed strange but I doubt they're related to the code you provided. 您描述的问题确实很奇怪,但是我怀疑它们与您提供的代码有关。

It could be thread safe only if all access too fooBarMap are synchronized. 仅当所有访问都fooBarMap同步时,它才可能是线程安全的。 A little out of scope, but safer may be to use a ConcurrentHashmap . 稍微超出范围,但使用ConcurrentHashmap可能更安全。

There is a great article on how hashmaps can be synchronized here . 对于如何包含HashMap可以同步一个伟大的文章在这里

  1. In situation like this it's best option to use ConcurrentHashMap . 在这种情况下,最好使用ConcurrentHashMap
  2. Verify if all Update-Read are in order. 验证所有更新读取是否都按顺序进行。
  3. As I understood from your question. 从您的问题中我了解到。 There are fix set of params which never changes. 有固定的params集,它们永远不会改变。 One of the ways I preferred in situation like this is: 在这种情况下,我偏爱的一种方法是:

    I. To create the map cache during start up and keep only one instance of it. I.在启动期间创建map cache并仅保留其中一个实例。

    II. II。 Read the map Instance anytime anywhere in the application. 随时随地在应用程序中读取map Instance

In the for loop you are returning reference to fooBar objects in the foobarList. 在for循环中,您将返回对foobarList中的fooBar对象的引用。

So the method calling getFooBar() has access to the Map through this fooBar reference object. 因此,调用getFooBar()的方法可以通过此fooBar引用对象访问Map。

try to clone fooBar before returning from getFooBar() 尝试从getFooBar()返回之前克隆fooBar

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM