Dlaczego ConcurrentHashMap zapobiega pustym kluczom i wartościom?

141

JavaDoc ConcurrentHashMapmówi tak:

Podobnie jak, Hashtableale w przeciwieństwie do HashMaptej klasy, ta klasa nie pozwala nullna użycie jako klucza lub wartości.

Moje pytanie: dlaczego?

Drugie pytanie: Dlaczego nie Hashtablezezwala na null?

Używałem wielu HashMap do przechowywania danych. Ale przy zmianie na ConcurrentHashMapkilka razy miałem kłopoty z powodu wyjątków NullPointerExceptions.

Marcel
źródło
1
Myślę, że to niezwykle irytująca niespójność. EnumMap również nie zezwala na null. Oczywiście nie ma ograniczeń technicznych uniemożliwiających stosowanie kluczy zerowych. dla Map <K, V> po prostu pole typu V zapewni obsługę kluczy o wartości null (prawdopodobnie inne pole logiczne, jeśli chcesz rozróżnić wartość pustą i brak wartości).
RAY
6
Lepszym pytaniem jest „dlaczego HashMap zezwala na klucz o wartości null i wartości null?”. Albo może „dlaczego Java pozwala null na umieszczanie wszystkich typów?” Lub nawet „dlaczego Java w ogóle ma wartości null?”.
Jed Wesley-Smith

Odpowiedzi:

220

Od samego autora ConcurrentHashMap(Doug Lea) :

Głównym powodem, dla którego wartości null nie są dozwolone w ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps), jest to, że nie można uwzględnić niejednoznaczności, które mogą być ledwo tolerowane w mapach innych niż współbieżne. Głównym z nich jest to, że jeśli map.get(key)powróci null, nie można wykryć, czy klucz jawnie mapuje się, nulla klucz nie jest mapowany. Na mapie, która nie jest współbieżna, możesz to sprawdzić za pośrednictwem map.contains(key), ale w przypadku mapy współbieżnej mapa mogła się zmieniać między wywołaniami.

Bruno
źródło
7
Dziękuję, ale co z null jako kluczem?
AmitW
2
Dlaczego nie użyć Optionals jako wartości wewnętrznie
benez
2
@benez Optionalto funkcja Java 8, która nie była wtedy dostępna (Java 5). OptionalRzeczywiście, możesz teraz użyć s.
Bruno
@AmitW, myślę, że odpowiedzi są takie same, tj. Niejasności. Na przykład załóżmy, że jeden wątek tworzy klucz jako null i przechowuje dla niego wartość. Następnie inny wątek zmienił inny klucz na null. Gdy drugi wątek spróbuje dodać nową wartość, zostanie on zastąpiony. Jeśli drugi wątek spróbuje uzyskać wartość, otrzyma wartość dla innego klucza, zmodyfikowanego przez pierwszy. Należy unikać takiej sytuacji.
Dexter
44

Uważam, że przynajmniej częściowo umożliwia to połączenie containsKeyi getutworzenie jednej rozmowy. Jeśli mapa może przechowywać wartości null, nie ma sposobu, aby stwierdzić, czy getzwraca wartość null, ponieważ nie było klucza dla tej wartości, czy tylko dlatego, że wartość była pusta.

Dlaczego to jest problemem? Ponieważ nie ma na to bezpiecznego sposobu. Weź następujący kod:

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

Ponieważ mjest to mapa współbieżna, klucz k może zostać usunięty między wywołaniami containsKeyi get, powodując, że ten fragment kodu zwraca wartość null, której nigdy nie było w tabeli, zamiast pożądanego KeyNotPresentException.

Zwykle można to rozwiązać przez synchronizację, ale z mapą współbieżną to oczywiście nie zadziała. W związku z tym podpis for getmusiał się zmienić, a jedynym sposobem, aby to zrobić w sposób zgodny z poprzednimi wersjami, było uniemożliwienie użytkownikowi wstawiania wartości null w pierwszej kolejności i dalsze używanie tego jako symbolu zastępczego dla „klucza nie znaleziono”.

Alice Purcell
źródło
Możesz to zrobić map.getOrDefault(key, NULL_MARKER). Jeśli tak null, wartość wynosiła null. Jeśli zwróci NULL_MARKER, wartość nie była obecna.
Oliv
@Oliv Tylko od Java 8. Ponadto może nie być sensownego znacznika zerowego dla tego typu.
Alice Purcell,
@AlicePurcell, "ale z mapą współbieżną, która oczywiście nie będzie działać" - dlaczego mogę podobnie synchronizować w wersji współbieżnej - więc zastanawiam się, dlaczego to nie zadziała. czy możesz to rozwinąć.
samshers
@samshers Żadne operacje na współbieżnej mapie nie są synchronizowane, więc musiałbyś zsynchronizować zewnętrznie wszystkie wywołania, w którym to momencie nie tylko straciłeś wszystkie korzyści płynące z posiadania mapy współbieżnej, ale zostawiłeś pułapkę dla przyszłych opiekunów, którzy naturalnie oczekiwaliby możliwości bezpiecznego dostępu do współbieżnej mapy bez synchronizacji.
Alice Purcell,
@AlicePurcell, świetnie. Choć jest to technicznie możliwe, z pewnością będzie to koszmar konserwacji i żaden późniejszy użytkownik nie będzie oczekiwać, że będą musieli synchronizować się w wersji współbieżnej.
samshers,
4

Josh Bloch zaprojektował HashMap; Zaprojektował Doug Lea ConcurrentHashMap. Mam nadzieję, że to nie jest zniesławiające. Właściwie myślę, że problem polega na tym, że wartości null często wymagają zawijania, aby prawdziwy null mógł oznaczać niezainicjowany. Jeśli kod klienta wymaga wartości null, może zapłacić (co prawda niewielki) koszt pakowania wartości null.

Tom Hawtin - haczyk
źródło
2

Nie możesz synchronizować wartości null.

Edycja: w tym przypadku nie jest to dokładnie powód. Początkowo myślałem, że dzieje się coś wymyślnego z blokowaniem rzeczy przed równoczesnymi aktualizacjami lub w inny sposób korzystaniem z monitora obiektów do wykrywania, czy coś zostało zmodyfikowane, ale po zbadaniu kodu źródłowego wydaje się, że się myliłem - blokują używając „segmentu” opartego na maska ​​bitowa skrótu.

W takim przypadku podejrzewam, że zrobili to, aby skopiować Hashtable, i podejrzewam, że Hashtable to zrobił, ponieważ w świecie relacyjnych baz danych null! = Null, więc użycie null jako klucza nie ma żadnego znaczenia.

Paul Tomblin
źródło
Co? Nie ma synchronizacji kluczy i wartości mapy. To nie miałoby sensu.
Tobias Müller,
Istnieją inne rodzaje blokowania. To sprawia, że ​​jest to „współbieżne”. Aby to zrobić, potrzebuje obiektu do zawieszenia.
Paul Tomblin,
2
Dlaczego wewnętrznie nie ma określonego obiektu, którego można by użyć do synchronizacji wartości null? np. „private Object NULL = new Object ();”. Myślę, że już to widziałem ...
Marcel,
Jakie inne rodzaje blokowania masz na myśli?
Tobias Müller,
Właściwie, teraz, gdy patrzę na kod źródłowy gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/ ... Mam co do tego poważne wątpliwości. Wygląda na to, że używa blokowania segmentów, a nie blokowania pojedynczych elementów.
Paul Tomblin,
0

ConcurrentHashMap jest bezpieczna wątkowo. Uważam, że niedopuszczenie pustych kluczy i wartości było częścią upewnienia się, że jest bezpieczny dla wątków.

Kevin Crowell
źródło
0

Wydaje mi się, że poniższy fragment dokumentacji API daje dobrą wskazówkę: „Ta klasa jest w pełni interoperacyjna z Hashtable w programach, które polegają na bezpieczeństwie wątków, ale nie na szczegółach synchronizacji”.

Prawdopodobnie chcieli tylko, aby w ConcurrentHashMappełni kompatybilny / wymienny z Hashtable. A ponieważ Hashtablenie zezwala na puste klucze i wartości.

Tobias Müller
źródło
2
I dlaczego Hashtable nie obsługuje wartości null?
Marcel
Patrząc na jego kod, nie widzę oczywistego powodu, dla którego Hashtable nie zezwala na wartości null. Może to była tylko decyzja API z czasów, gdy klasa została stworzona ?! HashMap ma specjalną obsługę wewnętrzną wielkości liter zerowych, czego Hashtable nie obsługuje. (Zawsze rzuca wyjątek NullPointerException.)
Tobias Müller
-2

Nie sądzę, aby odrzucenie wartości null było właściwą opcją. W wielu przypadkach chcemy umieścić klucz z wartością null w mapie współbieżnej. Jednak używając ConcurrentHashMap, nie możemy tego zrobić. Sugeruję, że nadchodząca wersja JDK może to wspierać.

yinhaomin
źródło
1
Czy myślałeś o rywalizacji o Javachampion?
BlackBishop
Użyj opcji Opcjonalne, jeśli chcesz, aby klucze zachowywały się jak null.
Alice Purcell