Коли слід використовувати ConcurrentSkipListMap?


82

У Java ConcurrentHashMapє найкраще multithreadingрішення. Тоді коли я повинен використовувати ConcurrentSkipListMap? Це надмірність?

Чи багатопотокові аспекти між цими двома є загальними?

Відповіді:


80

Ці два класи варіюються кількома способами.

ConcurrentHashMap не гарантує * час виконання своїх операцій у рамках свого контракту. Це також дозволяє налаштувати певні фактори навантаження (приблизно, кількість потоків, одночасно змінюючи його).

ConcurrentSkipListMap , з іншого боку, гарантує середню продуктивність O (log (n)) для широкого кола операцій. Він також не підтримує налаштування заради одночасності. ConcurrentSkipListMapтакож має ряд операцій, які ConcurrentHashMapцього не роблять: tapeEntry / 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

Відсортовані, навігаційні та одночасні

Див Пропустити Список для визначення структури даних.

А ConcurrentSkipListMapзберігає Mapв природному порядку ключів (або будь-який інший порядок ключів, який ви визначаєте). Так буде повільніше get/ put/ containsоперацій , ніж HashMap, але , щоб компенсувати це він підтримує SortedMap, NavigableMapі ConcurrentNavigableMapінтерфейси.


8

Що стосується продуктивності, skipListколи використовується як карта - здається в 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);
            }
        }
    }
}

2
Я думаю, що ConcurrentSkipListMap слід порівнювати з їх неконкурентною частиною лічильника, TreeMap.
Аббас

2
Як прокоментував Аббас, порівняння продуктивності з ConcurrentHashMap здається безглуздим. Метою ConcurrentSkipListMap є (a) забезпечення паралельності та (b) підтримка порядку сортування між ключами. ConcurrentHashMap робить a, але не b. Порівнювати швидкість від 0 до 60 Tesla і самоскида безглуздо, оскільки вони слугують різним цілям.
Василь Бурк

Хоча ви не знаєте показників ефективності, ви не знаєте, хто з них 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відповідних класів та інтерфейсу .
Василь Бурк,

1

На основі робочих навантажень ConcurrentSkipListMap може бути повільнішим, ніж TreeMap, із синхронізованими методами, як у KAFKA-8802, якщо потрібні запити про діапазон.


Дякую, що поділились. Я просто думаю замінити TreeMap на ConcurrentSkipListMap в одному зі своїх проектів, тому добре це знати! Чи є у вас більше контексту щодо того, чому ConcurrentSkipListMap працює повільніше, і більше деталей про порівняння продуктивності?
yusong

0

ConcurrentHashMap: якщо вам потрібен багатопотоковий індекс на основі get / put, підтримуються лише операції на основі індексу. Отримати / поставити з O (1)

ConcurrentSkipListMap: Більше операцій, ніж просто отримання / розміщення, наприклад, сортування верхнього / нижнього n елементів за ключем, отримання останнього входу, отримання / обхід цілої карти, відсортованої за ключем тощо. такий же великий, як ConcurrentHashMap. Це не реалізація ConcurrentNavigableMap із SkipList.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.