首页 > 解决方案 > 如何在 executorCompletionService 中使用 concurrentHashMap?

问题描述

我多次搜索数据库,即使我缓存了一些结果,仍然花费了很长时间。

List<Map<Long, Node>> aNodeMapList = new ArrayList<>();
Map<String, List<Map<String, Object>>> cacheRingMap = new ConcurrentHashMap<>();
for (Ring startRing : startRings) {
    for (Ring endRing : endRings) {
        Map<String, Object> nodeMapResult = getNodeMapResult(startRing, endRing, cacheRingMap);
        Map<Long, Node> nodeMap = (Map<Long, Node>) nodeMapResult.get("nodeMap");
        if (nodeMap.size() > 0) {
            aNodeMapList.add(nodeMap);
        }
    }
}

getNodeMapResult 是一个根据startRing, endRing, 来查找数据库的函数,缓存在cacheRingMap里面,下次如果发现结果已经存在于里面可能就不需要查找数据库了 cacheRingMap

我的领导告诉我,可以使用多线程技术。所以我把它改成了executorCompletionService,但是现在我有一个问题,当我使用concurrentHashMap缓存executorCompletionService的结果时,这个线程安全吗?换了以后会不会跑的快?

int totalThreadCount = startRings.size() * endRings.size();
ExecutorService threadPool2 = Executors.newFixedThreadPool(totalThreadCount > 4 ? 4 : 2);
CompletionService<Map<String, Object>> completionService = new ExecutorCompletionService<Map<String, Object>>(threadPool2);
for (Ring startRing : startRings) {
    for (Ring endRing : endRings) {
        completionService.submit(new Callable<Map<String, Object>>() {
            @Override
            public Map<String, Object> call() throws Exception {
                return getNodeMapResult(startRing, endRing, cacheRingMap);
            }
        });
    }
}

for (int i = 0; i < totalThreadCount; i++) {
    Map<String, Object> nodeMapResult = completionService.take().get();
    Map<Long, Node> nodeMap = (Map<Long, Node>) nodeMapResult.get("nodeMap");
    if (nodeMap.size() > 0) {
        aNodeMapList.add(nodeMap);
    }
}

标签: javamultithreadingthread-safety

解决方案


当我使用 concurrentHashMap 在 executorCompletionService 中缓存结果时,这个线程是否安全?

ConcurrentHashMap顾名思义,它本身是线程安全的(“并发”)。但是,这并不意味着使用它的代码是线程安全的。

例如,如果您的代码执行以下操作:

SomeObject object = cacheRingMap.get(someKey); //get from cache
if (object == null){ //oh-oh, cache miss
    object = getObjectFromDb(someKey); //get from the db
    cacheRingMap.put(someKey, object); //put in cache for next time
}

由于在此示例中getput没有以原子方式执行,因此执行此代码的两个线程最终可能会首先在缓存中查找相同的键,然后在 db.xml 中查找相同的键。它仍然是线程安全的,但我们执行了两次数据库查找而不是一次。但这只是一个简单的示例,更复杂的缓存逻辑(例如,包括缓存失效和从缓存映射中删除的逻辑)最终不仅会浪费,而且实际上是不正确的。这完全取决于地图的使用方式以及您需要从中获得什么保证。我建议您阅读ConcurrentHashMap javadoc。看看它能保证什么,不能保证什么。

换了以后会不会跑得快?</p>

这取决于太多需要提前知道的参数。数据库将如何处理并发查询?有多少查询?单个查询有多快?等等。最好的了解方法是实际尝试一下。

附带说明一下,如果您正在寻找提高性能的方法,您可能想尝试使用批处理查询。然后流程将是在缓存中搜索您需要的所有键,收集您需要查找的键,然后在一个查询中将它们一起发送到数据库。在许多情况下,单个大型查询会比一堆较小的查询运行得更快。

此外,在您的情况下,您应该检查地图中的并发查找是否比单线程查找更快。也许仅并行化查询本身,而不是缓存查找可以在您的情况下产生更好的结果。


推荐阅读