Когда мне следует использовать ConcurrentSkipListMap?

82

В Java ConcurrentHashMapесть лучшее multithreadingрешение. Тогда когда я должен использовать ConcurrentSkipListMap? Это избыточность?

Являются ли аспекты многопоточности между этими двумя общими?

DKSathore
источник

Ответы:

80

Эти два класса различаются несколькими способами.

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

ConcurrentSkipListMap , с другой стороны, гарантирует среднюю производительность O (log (n)) для широкого спектра операций. Он также не поддерживает настройку ради параллелизма. ConcurrentSkipListMapтакже имеет ряд операций, которых ConcurrentHashMapнет: потолокEntry / Key, floorEntry / Key и т. д. Он также поддерживает порядок сортировки, который в противном случае пришлось бы рассчитывать (с заметными затратами), если бы вы использовали ConcurrentHashMap.

По сути, для разных вариантов использования предусмотрены разные реализации. Если вам нужно быстрое добавление пары ключ / значение и быстрый поиск по одному ключу, используйте расширение HashMap. Если вам нужен более быстрый обход по порядку и вы можете позволить себе дополнительную стоимость вставки, используйте метод SkipListMap.

* Хотя я ожидаю, что реализация примерно соответствует общим гарантиям хэш-карты вставки / поиска O (1); игнорирование повторного хеширования

Кевин Монтроуз
источник
Хорошо. Log (n) в порядке, но действительно ли ConcurrentSkipListMap занимает мало места?
DKSRathore
1
Списки пропусков обязательно больше, чем Hashtables, но настраиваемый характер ConcurrentHashMap позволяет создавать Hashtable, размер которого превышает эквивалентный ConcurrentSkipListMap. В общем, я бы ожидал, что список пропуска будет больше, но того же порядка.
Кевин Монтроуз
«Он также не поддерживает настройку ради параллелизма» .. Почему? Какая ссылка?
Pacerier
2
@Pacerier - я не имел в виду, что он поддерживает настройку, потому что он одновременный, я имею в виду, что он не позволяет настраивать параметры, которые влияют на его одновременную производительность (в то время как ConcurrentHashMap это делает).
Кевин Монтроуз
@KevinMontrose Ic, значит, вы имели в виду: «Он также не поддерживает настройку параллелизма».
Pacerier
17

Сортировка, возможность навигации и одновременное выполнение

См. Список пропуска для определения структуры данных.

A ConcurrentSkipListMapхранит Mapв естественном порядке своих ключей (или в другом определенном вами порядке ключей). Так будет медленнее get/ put/ containsопераций , чем HashMap, но , чтобы компенсировать это он поддерживает SortedMap, NavigableMapи ConcurrentNavigableMapинтерфейсы.

Джим Ферранс
источник
8

С точки зрения производительности skipListпри использовании в качестве Map - оказывается в 10-20 раз медленнее. Вот результат моих тестов (Java 1.8.0_102-b14, win x32)

Benchmark                    Mode  Cnt  Score    Error  Units
MyBenchmark.hasMap_get       avgt    5  0.015 ?  0.001   s/op
MyBenchmark.hashMap_put      avgt    5  0.029 ?  0.004   s/op
MyBenchmark.skipListMap_get  avgt    5  0.312 ?  0.014   s/op
MyBenchmark.skipList_put     avgt    5  0.351 ?  0.007   s/op

И в дополнение к этому - вариант использования, когда действительно имеет смысл сравнивать одно с другим. Реализация кеширования последних недавно использованных элементов с использованием обеих этих коллекций. Теперь эффективность skipList выглядит более сомнительной.

MyBenchmark.hashMap_put1000_lru      avgt    5  0.032 ?  0.001   s/op
MyBenchmark.skipListMap_put1000_lru  avgt    5  3.332 ?  0.124   s/op

Вот код для JMH (выполняется как java -jar target/benchmarks.jar -bm avgt -f 1 -wi 5 -i 5 -t 1)

static final int nCycles = 50000;
static final int nRep = 10;
static final int dataSize = nCycles / 4;
static final List<String> data = new ArrayList<>(nCycles);
static final Map<String,String> hmap4get = new ConcurrentHashMap<>(3000, 0.5f, 10);
static final Map<String,String> smap4get = new ConcurrentSkipListMap<>();

static {
    // prepare data
    List<String> values = new ArrayList<>(dataSize);
    for( int i = 0; i < dataSize; i++ ) {
        values.add(UUID.randomUUID().toString());
    }
    // rehash data for all cycles
    for( int i = 0; i < nCycles; i++ ) {
        data.add(values.get((int)(Math.random() * dataSize)));
    }
    // rehash data for all cycles
    for( int i = 0; i < dataSize; i++ ) {
        String value = data.get((int)(Math.random() * dataSize));
        hmap4get.put(value, value);
        smap4get.put(value, value);
    }
}

@Benchmark
public void skipList_put() {
    for( int n = 0; n < nRep; n++ ) {
        Map<String,String> map = new ConcurrentSkipListMap<>();

        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            map.put(key, key);
        }
    }
}

@Benchmark
public void skipListMap_get() {
    for( int n = 0; n < nRep; n++ ) {
        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            smap4get.get(key);
        }
    }
}

@Benchmark
public void hashMap_put() {
    for( int n = 0; n < nRep; n++ ) {
        Map<String,String> map = new ConcurrentHashMap<>(3000, 0.5f, 10);

        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            map.put(key, key);
        }
    }
}

@Benchmark
public void hasMap_get() {
    for( int n = 0; n < nRep; n++ ) {
        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            hmap4get.get(key);
        }
    }
}

@Benchmark
public void skipListMap_put1000_lru() {
    int sizeLimit = 1000;

    for( int n = 0; n < nRep; n++ ) {
        ConcurrentSkipListMap<String,String> map = new ConcurrentSkipListMap<>();

        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            String oldValue = map.put(key, key);

            if( (oldValue == null) && map.size() > sizeLimit ) {
                // not real lru, but i care only about performance here
                map.remove(map.firstKey());
            }
        }
    }
}

@Benchmark
public void hashMap_put1000_lru() {
    int sizeLimit = 1000;
    Queue<String> lru = new ArrayBlockingQueue<>(sizeLimit + 50);

    for( int n = 0; n < nRep; n++ ) {
        Map<String,String> map = new ConcurrentHashMap<>(3000, 0.5f, 10);

        lru.clear();
        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            String oldValue = map.put(key, key);

            if( (oldValue == null) && lru.size() > sizeLimit ) {
                map.remove(lru.poll());
                lru.add(key);
            }
        }
    }
}
Xtra Coder
источник
2
Я думаю, что ConcurrentSkipListMap следует сравнить с их параллельной частью, TreeMap.
аббас
2
Как прокомментировал Аббас, сравнение производительности с ConcurrentHashMap кажется глупым. Целью ConcurrentSkipListMap является (а) обеспечение параллелизма и (б) поддержание порядка сортировки между ключами. ConcurrentHashMap выполняет a, но не b. Сравнивать скорость от 0 до 60 у Tesla и самосвала бессмысленно, поскольку они служат разным целям.
Basil
Хотя вы не знаете показателей производительности, вы не знаете, какой из них - Tesla, а какой - «самосвал» :) Также ... вы не знаете цену «b» без показателей. Поэтому - сравнение производительности - вообще полезная вещь.
Xtra Coder
1
Может быть, добавить сравнение к древовидной карте! : D
simgineer
4

Тогда когда мне следует использовать ConcurrentSkipListMap?

Когда вам (а) нужно отсортировать ключи и / или (б) нужны функции первого / последнего, начала / конца и подкарты навигационной карты.

ConcurrentHashMapКласс реализует ConcurrentMapинтерфейс, как это делает ConcurrentSkipListMap. Но если вам также нужно поведение SortedMapи NavigableMap, используйтеConcurrentSkipListMap

ConcurrentHashMap

  • ❌ Сортировано
  • ❌ Навигация
  • ✅ одновременно

ConcurrentSkipListMap

  • ✅ Сортировано
  • ✅ Навигация
  • ✅ одновременно

В этой таблице вы познакомитесь с основными функциями различных Mapреализаций, связанных с Java 11. Щелкните / коснитесь для увеличения.

Таблица реализаций карт в Java 11, сравнение их возможностей

Помните, что вы можете получить другие Mapреализации и подобные структуры данных из других источников, таких как Google Guava .

Василий Бурк
источник
Эта картинка потрясающая. Есть ли у вас похожие изображения для некоторых или всех обычных и параллельных коллекций?
AnV
1
@Anv Спасибо, создание этой диаграммы потребовало немало усилий. Это часть моей презентации для групп пользователей Java: карта Java Maps . И нет, я сделал еще одну диаграмму классов для Stringсвязанных классов и интерфейса .
Basil Bourque
1

В зависимости от рабочих нагрузок ConcurrentSkipListMap может быть медленнее, чем TreeMap с синхронизированными методами, как в KAFKA-8802, если требуются запросы диапазона.

Анураг Шарма
источник
Спасибо, что поделился. Я просто подумываю заменить TreeMap на ConcurrentSkipListMap в одном из моих проектов, так что хорошо это знать! У вас есть больше информации о том, почему ConcurrentSkipListMap медленнее, и больше подробностей о сравнении производительности?
yusong
0

ConcurrentHashMap: когда вы хотите получить / положить многопоточный индекс, поддерживаются только операции на основе индекса. Get / Put имеют O (1)

ConcurrentSkipListMap: больше операций, чем просто получение / вставка, например, отсортированные верхние / нижние n элементов по ключу, получение последней записи, выборка / просмотр всей карты, отсортированной по ключу и т. Д. Сложность составляет O (log (n)), поэтому производительность размещения не так же хорошо, как ConcurrentHashMap. Это не реализация ConcurrentNavigableMap с SkipList.

Подводя итог, используйте ConcurrentSkipListMap, когда вы хотите выполнить больше операций на карте, требующих сортированных функций, а не просто получить и поместить.

ссоры
источник