Чому ConcurrentHashMap запобігає нульовим ключам і значенням?


141

JavaDoc ConcurrentHashMapговорить про це:

Як Hashtableі на відміну від HashMapцього, цей клас не дозволяє nullвикористовуватись як ключ або значення.

Моє запитання: Чому?

2-е питання: Чому не Hashtableдозволити нуль?

Я використовував багато HashMaps для зберігання даних. Але при переході до ConcurrentHashMapмене кілька разів потрапляли проблеми через NullPointerExceptions.


1
Я думаю, що це надзвичайно дратівлива непослідовність. EnumMap також не дозволяє нуль. Очевидно, немає технічного обмеження, яке відключає нульові ключі. для Map <K, V> просто поле, набране V, забезпечить підтримку нульових ключів (можливо, інше булеве поле, якщо ви хочете розмежувати нульове значення і без значення).
RAY

6
Краще питання - "чому HashMap допускає нульовий ключ і нульові значення?". Або, можливо, "чому Java дозволяє null населяти всі типи?", Або навіть "чому у Java взагалі є нулі?".
Джед Веслі-Сміт

Відповіді:


220

Від самого автора ConcurrentHashMap(Дуг Леа) :

Основна причина того, що нулі заборонені в ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps), полягає в тому, що двозначності, які можуть бути ледь терпимими в картах, що не є одночасними, не можуть бути розміщені. Основна з них полягає в тому, що якщо map.get(key)повертається null, ви не можете виявити, чи ключ явно відображається в nullпорівнянні з ключем, не відображається. У несумісній карті ви можете перевірити це за допомогою map.contains(key), але в паралельній карті карта може змінитися між викликами.


7
Дякую, а як же мати нуль як ключ?
AmitW

2
чому б не використовувати Optionals як значення внутрішньо
benez

2
@benez Optional- це функція Java 8, яка тоді була недоступна (Java 5). OptionalДійсно, ви можете використовувати s зараз.
Бруно

@AmitW, я думаю, що анс - це те саме, що і неясності. Наприклад, припустимо, що один потік робить ключ як нуль і зберігає проти нього значення. Потім інший потік змінив інший ключ на null. Коли друга нитка намагається додати нове значення, вона буде замінена. Якщо друга нитка спробує отримати значення, вона отримає значення для іншого ключа, модифікованого першим. Такої ситуації слід уникати.
Декстер

44

Я вважаю , що це, принаймні частково, дозволяють об'єднувати containsKeyі getв один виклик. Якщо карта може містити нулі, немає способу визначити, чи getповертає нуль, тому що для цього значення не було ключа або просто тому, що значення було нульовим.

Чому це проблема? Тому що немає безпечного способу зробити це самостійно. Візьміть наступний код:

if (m.containsKey(k)) {
   return m.get(k);
} else {
   throw new KeyNotPresentException();
}

Оскільки mце паралельна карта, ключ k може бути видалений між викликами containsKeyта getвикликами, внаслідок чого цей фрагмент повертає нуль, який ніколи не був у таблиці, а не бажаний KeyNotPresentException.

Зазвичай ви вирішите це шляхом синхронізації, але з одночасною картою, що, звичайно, не буде працювати. Отже, підпис для getдовелося змінити, і єдиним способом зробити це за допомогою сумісного ззаду способу було запобігти користувачеві вводити в першу чергу нульові значення та продовжувати використовувати це як заповнювач для "ключа не знайдено".


Можна зробити map.getOrDefault(key, NULL_MARKER). Якщо це так null, значення було null. Якщо воно повертається NULL_MARKER, значення не було.
Олів

@Oliv Тільки на Java 8. Також може не бути розумного нульового маркера для цього типу.
Аліса Перселл

@AlicePurcell, "але з одночасною картою, яка, звичайно, не буде працювати" - чому я можу синхронізуватися на одночасному варіанті аналогічно - тому цікаво, чому це не буде працювати. чи можете ви це детальніше розробити.
samshers

@samshers Жодна операція над одночасною картою не синхронізується, тому вам знадобиться зовнішньо синхронізувати всі дзвінки, і тоді ви не тільки втратили всі переваги від продуктивності одночасної карти, ви залишили пастку для майбутніх утримувачів, які Природно, сподіваємось, що зможете безпечно отримати доступ до паралельної карти без синхронізації.
Еліс Перселл

@AlicePurcell, чудово. Хоча технічно це можливо, це, безумовно, буде кошмаром технічного обслуговування, і пізніші користувачі цього не очікують, що їм доведеться синхронізувати сумісну версію.
самшери

4

Джош Блох розроблений HashMap; Дуг Лі розробив ConcurrentHashMap. Я сподіваюсь, що це не безглуздо. Насправді я думаю, що проблема полягає в тому, що нулі часто потребують обгортання, щоб справжній нуль міг стати неініціалізованим. Якщо клієнтський код вимагає нульових значень, то він може сам оплатити (правда, невелику) вартість загортання нулів.


2

Неможливо синхронізувати на нулі.

Редагувати: Це не зовсім так у цьому випадку. Спочатку я думав, що відбувається щось фантазійне з блокуванням речей проти одночасних оновлень або іншим способом використання монітора Об'єктів, щоб виявити, чи щось було змінено, але після вивчення вихідного коду виявляється, я помилявся - вони блокують за допомогою "сегмента" на основі бітмаска хешу.

У цьому випадку я підозрюю, що вони зробили це для копіювання Hashtable, і я підозрюю, що Hashtable це зробив, оскільки у світі реляційних баз даних null! = Null, тому використання null як ключа не має значення.


Так? Не відбувається синхронізація ключів та значень Карти. Це не мало б сенсу.
Тобіас Мюллер

Існують і інші види блокування. Ось що робить його "Погоджуваним". Для цього йому потрібно повісити Об’єкт.
Пол Томблін

2
Чому не існує внутрішнього специфічного об'єкта, який міг би бути використаний для синхронізації нульових значень? наприклад, "приватний об'єкт NULL = новий об'єкт ();". Я думаю, я це бачив і раніше ...
Марсель

Які ще види блокування ви маєте на увазі?
Тобіас Мюллер

Насправді, тепер, коли я дивлюсь на вихідний код gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/…, я маю серйозні сумніви з цього приводу. Здається, він використовує блокування сегментів, а не блокування окремих елементів.
Пол Томблін

0

ConcurrentHashMap є безпечним для потоків. Я вважаю, що заборона нульових ключів і значень була частиною того, щоб переконатися в безпеці потоку.


0

Я думаю, що наступний фрагмент документації API дає хороший натяк: "Цей клас повністю взаємодіє з Hashtable в програмах, які покладаються на його безпеку потоку, але не на його дані про синхронізацію."

Вони, ймовірно, просто хотіли зробити ConcurrentHashMapповністю сумісні / взаємозамінні Hashtable. І як Hashtableне дозволяє нульові ключі та значення ..


2
І чому Hashtable не підтримує нуль?
Марсель

Переглядаючи його код, я не бачу очевидної причини, чому Hashtable не дозволяє нульових значень. Можливо, це було просто рішення API, коли було створено клас ?! HashMap має спеціальну обробку для нульового випадку всередині, чого Hashtable не робить. (Це завжди кидає NullPointerException.)
Тобіас Мюллер

-2

Я не думаю, що заборона нульового значення є правильним варіантом. У багатьох випадках ми хочемо ввести ключ з нульовим значенням на поточну карту. Однак, використовуючи ConcurrentHashMap, ми не можемо цього зробити. Я вважаю, що наступна версія JDK може це підтримати.


1
Чи думали ви про те, щоб змагатися за Javachampion?
BlackBishop

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