У Java ConcurrentHashMap
є найкраще multithreading
рішення. Тоді коли я повинен використовувати ConcurrentSkipListMap
? Це надмірність?
Чи багатопотокові аспекти між цими двома є загальними?
У Java ConcurrentHashMap
є найкраще multithreading
рішення. Тоді коли я повинен використовувати ConcurrentSkipListMap
? Це надмірність?
Чи багатопотокові аспекти між цими двома є загальними?
Відповіді:
Ці два класи варіюються кількома способами.
ConcurrentHashMap не гарантує * час виконання своїх операцій у рамках свого контракту. Це також дозволяє налаштувати певні фактори навантаження (приблизно, кількість потоків, одночасно змінюючи його).
ConcurrentSkipListMap , з іншого боку, гарантує середню продуктивність O (log (n)) для широкого кола операцій. Він також не підтримує налаштування заради одночасності. ConcurrentSkipListMap
також має ряд операцій, які ConcurrentHashMap
цього не роблять: tapeEntry / Key, floorEntry / Key тощо. Він також підтримує порядок сортування, який інакше потрібно було б обчислити (за помітних витрат), якщо ви використовували ConcurrentHashMap
.
В основному, різні реалізації передбачені для різних випадків використання. Якщо вам потрібне швидке додавання пари один ключ / значення та швидке пошук одного ключа, використовуйте HashMap
. Якщо вам потрібен швидший обхід в порядку і ви можете дозволити собі додаткові витрати на вставку, скористайтесь SkipListMap
.
* Хоча, я гадаю, реалізація приблизно відповідає загальним гарантіям хеш-карти вставки / пошуку O (1); ігноруючи повторне хешування
Див Пропустити Список для визначення структури даних.
А ConcurrentSkipListMap
зберігає Map
в природному порядку ключів (або будь-який інший порядок ключів, який ви визначаєте). Так буде повільніше get
/ put
/ contains
операцій , ніж HashMap
, але , щоб компенсувати це він підтримує SortedMap
, NavigableMap
і ConcurrentNavigableMap
інтерфейси.
Що стосується продуктивності, 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);
}
}
}
}
Тоді коли я повинен використовувати ConcurrentSkipListMap?
Коли (а) вам потрібно зберегти сортування ключів та / або (б) вам потрібні перші / останні, голова / хвіст та підкарти особливостей навігаційної карти.
ConcurrentHashMap
Клас реалізує ConcurrentMap
інтерфейс, як це робить ConcurrentSkipListMap
. Але якщо ви також хочете поведінку SortedMap
і NavigableMap
, використовуйтеConcurrentSkipListMap
ConcurrentHashMap
ConcurrentSkipListMap
Ось таблиця, яка проведе вас через основні особливості різних Map
реалізацій в комплекті з Java 11. Клацніть / торкніться, щоб збільшити.
Майте на увазі, що ви можете отримати інші Map
реалізації та подібні структури даних з інших джерел, таких як Google Guava .
String
відповідних класів та інтерфейсу .
На основі робочих навантажень ConcurrentSkipListMap може бути повільнішим, ніж TreeMap, із синхронізованими методами, як у KAFKA-8802, якщо потрібні запити про діапазон.
ConcurrentHashMap: якщо вам потрібен багатопотоковий індекс на основі get / put, підтримуються лише операції на основі індексу. Отримати / поставити з O (1)
ConcurrentSkipListMap: Більше операцій, ніж просто отримання / розміщення, наприклад, сортування верхнього / нижнього n елементів за ключем, отримання останнього входу, отримання / обхід цілої карти, відсортованої за ключем тощо. такий же великий, як ConcurrentHashMap. Це не реалізація ConcurrentNavigableMap із SkipList.
Підсумовуючи підсумок, використовуйте ConcurrentSkipListMap, коли ви хочете зробити більше операцій на карті, що вимагають відсортованих функцій, а не просто отримати та поставити.