简体   繁体   English

没有同步关键字的ConcurrentHashMap

[英]ConcurrentHashMap without synchronized keyword

I have a ConcurrentHashMap<String,List<String>> that is accessed by threads which check if the key exists before appending a value. 我有一个ConcurrentHashMap<String,List<String>> ,该线程可以通过在附加值之前检查键是否存在的线程来访问。 I got this working with the use of synchronized keyword. 我通过使用synchronized关键字来完成此工作。 If synchronized keyword is not used then the values are wrong. 如果未使用synchronized关键字,则值是错误的。 Isn't ConcurrentHashMap threadsafe? ConcurrentHashMap线程安全吗? Or is there a issue with this code? 还是此代码有问题? Is is possible to get this code working without using synchronized to achieve better performance? 是否可以在不使用synchronized情况下使此代码正常工作以获得更好的性能? Here is the code that does that. 这是执行此操作的代码。

ExecutorService executorService = Executors.newFixedThreadPool(4);
        final ConcurrentLinkedQueue<Future<?>> futures = new ConcurrentLinkedQueue<Future<?>>();
        final ConcurrentHashMap<String, List<String>> map = new ConcurrentHashMap<String, List<String>>();
        final JsonParser parser = new JsonParser();
        File[] files = new File(dir).listFiles();
for (final File tfile : files) {
                futures.add((Future<String>) executorService.submit(new Runnable() {
                    public void run() {
                        Object obj = parser.parse(new FileReader(tfile.getAbsolutePath()));
                        JsonObject jsonObject = (JsonObject) obj;
                        String documentname = obj.get("name").toString();
                        synchronized (map) {
                            List<String> paths = new ArrayList<String>();
                            //if key already exists append new path to the value
                            if (map.containsKey(documentname)) {
                                paths = map.get(documentname);
                            }
                            paths.add(tfile.getAbsolutePath());
                            map.put(documentname, paths);
                        }
                    }
                }));
            }

You could try replacing this: 您可以尝试替换此:

String documentname = obj.get("name").toString();
List<String> paths = new ArrayList<String>();
//if key already exists append new path to the value
if (map.containsKey(documentname)) {
    paths = map.get(documentname);
}
paths.add(tfile.getAbsolutePath());
map.put(documentname, paths);

with this: 有了这个:

String documentname = obj.get("name").toString();
List<String> paths = Collections.synchronizedList(new ArrayList<String>());
List<String> existing = map.putIfAbsent(documentname, paths);
if (existing != null) {
    paths = existing;
}
paths.add(tfile.getAbsolutePath());

The putIfAbsent method avoids a race condition that occurs if two threads both try to check (using containsKey) then put an entry into the map. 如果两个线程都试图检查(使用containsKey)然后将一个条目放入映射中,则putIfAbsent方法可避免出现竞争情况。

The synchronizedList method wraps the nested collection with a synchronized wrapper, so that you don't need to synchronize accesses to the nested collection. synchronizedList方法使用同步包装器包装嵌套集合,因此您无需同步对嵌套集合的访问。 Alternatively, you could use a concurrent data structure from java.util.concurrent. 另外,您可以使用java.util.concurrent中的并发数据结构。

ThreadSafe is a static analysis tool that finds concurrency bugs. ThreadSafe是一个静态分析工具,可发现并发错误。 It can run standalone, in Eclipse or be used as a SonarQube plugin. 它可以在Eclipse中独立运行,也可以用作SonarQube插件。 In particular, it has a checkers for the two bugs in the code you've shown: 特别是,它在您显示的代码中有一个检查两个错误的检查器:

  • Non-atomic use of get-check-put (which can be replaced with putIfAbsent) ( example report ) 非原子使用get-check-put(可以用putIfAbsent代替)( 示例报告
  • Shared non-threadsafe content in a concurrent collection ( example report ) 并发集合中共享的非线程安全内容( 示例报告

One problem with the code using putIfAbsent that I've suggested is that it always creates a collection. 我建议使用putIfAbsent编写的代码的一个问题是,它总是创建一个集合。 When the map already contains an entry for the given key, the new collection is simply discarded. 当映射已经包含给定键的条目时,将仅丢弃新集合。 However, this may be inefficient for your application, and can put extra pressure on the garbage collector. 但是,这可能对您的应用程序效率不高,并且可能对垃圾收集器施加额外的压力。

Therefore, you might want to consider something like this: 因此,您可能需要考虑以下内容:

String documentname = obj.get("name").toString();
List<String> paths = map.get(documentname);
if (paths == null) {
   paths = Collections.synchronizedList(new ArrayList<String>());
   List<String> existing = map.putIfAbsent(documentname, paths);
   if (existing != null) {
       paths = existing;
   }
   paths.add(tfile.getAbsolutePath());
}

Note the "Rule Description" link in bottom-left of the screen in this ThreadSafe non-atomic get-check-put link. 请注意此ThreadSafe 非原子的get-check-put链接中屏幕左下方的“规则描述”链接。 Click the "Rule Description" link for further explanation of the get-check-put problem, and possible solutions to it. 单击“规则描述”链接,以获取有关“获取-检查-放置”问题的进一步说明以及可能的解决方案。

ConcurrentHashMap is thread-safe, but that doesn't mean anything you do with a ConcurrentHashMap is. ConcurrentHashMap是线程安全的,但这并不意味着您对ConcurrentHashMap所做的任何事情。 There are several problems in the code, if synchronized isn't used: 如果不使用同步,代码中存在几个问题:

  1. You're accessing an ArrayList concurrently, from multiple threads. 您正在从多个线程同时访问ArrayList。 An ArrayList is not thread-safe ArrayList不是线程安全的
  2. You're doing several operations ( containKey , get , put ) in sequence on the Map. 您正在containKey执行几个操作( containKeygetput )。 Each of these operations is thread-safe and atomic, but the sequence of operations is not. 这些操作都是线程安全的和原子的,但操作顺序不是。 If you need that sequence of operations to be atomic, then you need to synchronize. 如果您需要该操作序列是原子的,则需要进行同步。 Or you need to use an equivalent atomic operation: putIfAbsent (). 或者您需要使用等效的原子操作: putIfAbsent ()。

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

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