Когда использовать concurrenthashmap против hashmap для put и get - PullRequest
0 голосов
/ 06 сентября 2018

В вопросе об интервью меня попросили объяснить ситуацию, когда использование concurrenthashmap было бы правильным способом по сравнению с использованием hashmap. На плате было два столбца t1 и t2 (соответствующих thread1 и thread2), и я должен был написать последовательность действий (например, map.put(2, 10), map.get(2) и т. Д.), Которые при использовании concurrenthashmap против hashmap могли бы создать ожидаемый результат.

Я пытался привести пример с итератором, но это было не то, что искал интервьюер. Он искал последовательность операций put и get для thread1 и thread2. Он сказал, предположим, что мы никогда не повторяем, и мы только выполняем операции put и get.

Я посмотрел на другие потоки на SO и подтвердил свои знания о безопасности потоков, но все еще не могу придумать ни одного примера для put и получаю неправильный результат с hashmap и правильный результат с concurrenthashmap. Есть ли какая-то последовательность «пут» и «пут», или я должен был сказать, что это невозможно?

Ответы [ 2 ]

0 голосов
/ 06 сентября 2018

Это интересный вопрос.

Правильный ответ:

Существует несколько реалистичных случаев, когда последовательность операций get и put для ConcurrentHashMap даст ожидаемый результат в многопоточном сценарии. Вместо put() вам почти всегда нужно использовать атомарные операции сравнения и изменения , такие как computeIfAbsent(), чтобы сделать что-нибудь полезное. Единственным исключением является случай, когда вы используете карту в качестве кэша, и возможность того, чтобы несколько потоков вычисляли одну и ту же запись, более эффективна, чем блокировка, пока один поток делает это ... но тогда вам действительно нужен кэш? Не очень часто.

Только для записи, это выглядело бы так:

Thread1 + Thread2 (they both do the same thing)
-----------------------------------------------

result = map.get(key);
if (result == null) {
    result = somewhat_expensive_function(key)
    map.put(key, result);
}
return result; 

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

Если бы я задал этот вопрос в интервью, я бы протестировал следующее: понимает ли кандидат, что использование потоковых структур данных не делает его алгоритм поточно-безопасным?

0 голосов
/ 06 сентября 2018

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

Однако часто вы должны получить более благоприятные эффекты. Приведенный ниже код должен поместить 2000 записей в каждую карту из нескольких потоков. Но для HashMap после операции на карте будет последовательно меньше записей, чем 2000, поскольку некоторые из пут будут конфликтовать друг с другом, и их результат будет потерян.

public class BreakingMap {
    public static void testIt(Map<Integer, Integer> map) throws InterruptedException {
        IntStream.range(0, 2000).parallel().forEach(i -> map.put(i, -1));
        System.out.println(map.size());
    }

    public static void main(String[] args) throws InterruptedException {
        testIt(new HashMap<>());
        testIt(new ConcurrentHashMap<>());
    }
}
...