我有一个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);
}
}
}));
}
答案 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插件。特别是,它为您显示的代码中的两个错误提供了一个检查器:
我建议使用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做的任何事情。如果未使用同步,则代码中存在几个问题:
containKey
,get
,put
)。这些操作中的每一个都是线程安全的和原子的,但操作顺序不是。如果您需要将该操作序列设置为原子操作,则需要进行同步。或者您需要使用等效的原子操作:putIfAbsent
()。