Производительность, когда поток чтения и записи на ConcurrentHashMap совпадает - PullRequest
0 голосов
/ 02 мая 2020

В интервью интервьюер спросил меня, чем ConcurrentHashMap отличается от HashTable. Я просто хочу обсудить вопрос, в котором интервьюер не убедился. Я сказал, что в ConcurrentHashMap любое количество потоков может одновременно выполнять операцию чтения, тогда как в HashTable одновременно может выполняться только один поток. Затем он дал сценарий для ConcurrentHashMap, предположив, что один поток пишет в одном сегменте, и в то же время другой поток читает его значение. Будет ли второй поток заблокирован ?? Я сказал нет, но он не был убежден. Я проверил в javado c, который говорит ..

Операции получения (в том числе get) обычно не блокируются, поэтому могут перекрываться с операциями обновления (включая put и remove). Извлечения отражают результаты самых последних завершенных операций обновления, сохраняющих их начало.

В нем говорится, что операции извлечения не блокируются, а добавляются generally что это значит ??

Для этого я сделал программа, в которой два потока выполняют операции чтения и записи в ConcurrentHashMap и HashTable, а также с synchronizedMap: я выполняю операции чтения и записи в одном и том же сегменте в течение одной секунды

class ReaderThread extends Thread {

    private Map<String, Integer> map;
    private static boolean b = false;

    public ReaderThread(Map<String, Integer> map) {
        this.map = map;
    }

    public void run() {
        long startTime = System.nanoTime();
        long endTime = 0;
        while (!b) {
            map.get("A");
            endTime = System.nanoTime();
            if (endTime - startTime > 1000000000L)
                b = true;
        }
    }
}

class WriterThread extends Thread {

    private Map<String, Integer> map;
    private static int n = 0;
    private static boolean b = false;

    public WriterThread(Map<String, Integer> map) {
        this.map = map;
    }

    public void run() {
        long startTime = System.nanoTime();
        long endTime = 0;
        while (!b) {
            map.put("A", n++);
            endTime = System.nanoTime();
            if (endTime - startTime > 1000000000L)
                b = true;
        }
    }
}

public class DiffrentMapReadWritePerformanceTest {
    public static void main(String[] args) throws InterruptedException {
        Map<String, Integer> map = new ConcurrentHashMap<>();
//      Map<String, Integer> map = new Hashtable<>();       
//      Map<String, Integer> map = new HashMap<>();
//      map = Collections.synchronizedMap(map);
        Thread readerThread = new ReaderThread(map);
        Thread writerThread = new WriterThread(map);

        writerThread.start();
        readerThread.start();
        writerThread.join();
        readerThread.join();
        System.out.println(map.get("A"));
    }
}

и O / P на основе различных объектов карты : ConcurrentHashMap: 8649407 Хеш-таблица: 5284068 synchronizedMap: 5438039

Следовательно, вывод подтверждает, что ConcurrentHashMap работает быстро относительно HashTable в многопоточной среде, но не доказывает, что во время записи потока записи поток чтения не был заблокирован для чтения. Есть ли способ подтвердить это ??

Ответы [ 2 ]

3 голосов
/ 03 мая 2020

Этот комментарий JavaDo c был написан для самой ранней версии класса. Это обеспечивает некоторую гибкость реализации и допускает эволюцию.

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

В Java 8 га sh Стол был переделан из грубых сегментов в мелкозернистые бункеры. Однако computeIfAbsent был пессимистичен c, всегда блокируя чтение или вычисление значения. В Java 9 это было улучшено за счет избежания блокировки, когда запись находится в начале корзины, но блокировок для сканирования, если нет. В таких случаях, как кэш, этого может быть недостаточно, поэтому Caffeine всегда выполняет оптимизацию c get перед вычислением. В этих случаях это может привести к выигрышу в производительности dramati c.

Это означает, что для извлечения может возникнуть блокировка, но не так, как дизайн развивается, чтобы ее избежать. Расплывчатая формулировка позволяет сохранить классовый контракт, пока изменяется реализация.

1 голос
/ 02 мая 2020

Насколько я знаю, и я, возможно, ошибаюсь, я бы сказал, что вы правы. Операция get не получает блокировку, но поддерживает порядок обновления перед обновлением. Это означает, что любое обновление, которое происходит до получения, должно быть отражено в этом поиске. Операция put, с другой стороны, получает блокировку для каждого бина, где bin получается путем хеширования значения объекта. Поэтому для записи / обновления хэш-карты требуется блокировка. Как указано в javadocs, вероятность того, что потоки одновременно обращаются к одному и тому же элементу, низкая.

...