Как использовать concurrentHashMap в executorCompletionService? - PullRequest
1 голос
/ 15 октября 2019

Я выполняю поиск в базе данных много раз, даже если кеширую какой-то результат, он все еще стоит много времени.

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

1 Ответ

1 голос
/ 15 октября 2019

Безопасен ли этот поток, когда я использую concurrentHashMap для кэширования результата в executorCompletionService?

* * * * * * * * * * * * * * * * * * * * * * * * * * является поточно-ориентированным, как следует из его названия («Concurrent»)Однако это не означает, что код, который его использует, является потокобезопасным.

Например, если ваш код выполняет следующие действия:

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
}

Поскольку get и put не выполняются атомарно в этом примере, два потока, выполняющие этот код, могут в конечном итоге оба искать один и тот же ключ сначала в кеше, а затем в БД. Он по-прежнему поточно-ориентирован, но мы выполнили два просмотра БД вместо одного. Но это всего лишь простой пример, более сложная логика кэширования (скажем, такая, которая включает в себя аннулирование кэша и удаление из карты кэша) может оказаться не просто бесполезной, но и фактически неверной. Все зависит от того, как карта используется и что гарантирует вам от нее. Я предлагаю вам прочитать ConcurrentHashMap javadoc . Посмотрите, что он может гарантировать, а что нет.

Будет ли он работать быстро после того, как я изменюсь?

Это зависит от слишком большого количества параметров, которые необходимо знать заранее. Как база данных будет обрабатывать параллельные запросы? Сколько запросов есть? Как быстро выполняется один запрос? И т.д. Лучший способ узнать это на самом деле попробовать.

В качестве примечания: если вы ищете способы повышения производительности, вы можете попробовать использовать пакетный запрос. Тогда потоком будет поиск в кеше всех необходимых вам ключей, сбор ключей, которые нужно найти, а затем отправка их всех вместе в одном запросе к базе данных. Во многих случаях один большой запрос будет выполняться быстрее, чем куча меньших.

Кроме того, вы должны проверить, быстрее ли в вашем случае одновременные поиски на карте, чем однопоточные. Возможно, распараллеливание только самого запроса, а не поиска в кэше, может дать лучшие результаты в вашем случае.

...