ConcurrentHashMap没有synchronized关键字

时间:2015-10-28 18:40:52

标签: java multithreading

我有一个ConcurrentHashMap<String,List<String>>由线程访问,在追加值之前检查密钥是否存在。我使用synchronized关键字来解决这个问题。如果未使用synchronized关键字,则值为错误。 ConcurrentHashMap线程不安全吗?或者这段代码有问题吗?是否可以在不使用synchronized的情况下使此代码正常工作以获得更好的性能?这是执行此操作的代码。

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

2 个答案:

答案 0 :(得分:3)

你可以尝试替换它:

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

用这个:

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());

putIfAbsent方法避免了两个线程都试图检查(使用containsKey)然后将一个条目放入地图时发生的竞争条件。

synchronizedList方法使用同步包装器包装嵌套集合,因此您无需同步对嵌套集合的访问。或者,您可以使用java.util.concurrent中的并发数据结构。

ThreadSafe是一个静态分析工具,可以找到并发错误。它可以在Eclipse中独立运行,也可以用作SonarQube插件。特别是,它为您显示的代码中的两个错误提供了一个检查器:

  • 非原子使用get-check-put(可以替换为putIfAbsent)(example report
  • 并发集合中共享的非线程安全内容(example report

我建议使用putIfAbsent的代码的一个问题是它总是创建一个集合。当映射已包含给定键的条目时,将简单地丢弃新集合。但是,这个可能对您的应用程序来说效率低下,并且会给垃圾收集器带来额外的压力。

因此,您可能需要考虑这样的事情:

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

请注意此ThreadSafe non-atomic get-check-put链接中屏幕左下方的“规则说明”链接。单击“规则描述”链接以获取有关get-check-put问题的更多说明,以及可能的解决方案。

答案 1 :(得分:2)

ConcurrentHashMap是线程安全的,但这并不代表你使用ConcurrentHashMap做的任何事情。如果未使用同步,则代码中存在几个问题:

  1. 您正在从多个线程同时访问ArrayList。 ArrayList不是线程安全的
  2. 您正在地图上按顺序执行多项操作(containKeygetput)。这些操作中的每一个都是线程安全的和原子的,但操作顺序不是。如果您需要将该操作序列设置为原子操作,则需要进行同步。或者您需要使用等效的原子操作:putIfAbsent()。