Por que o ConcurrentHashMap impede chaves e valores nulos?

141

O JavaDoc de ConcurrentHashMapdiz o seguinte:

De maneira semelhante, Hashtablemas diferente HashMap, essa classe não permite nullser usada como chave ou valor.

Minha pergunta: por que?

2ª pergunta: Por que não Hashtablepermite nulo?

Eu usei muitos HashMaps para armazenar dados. Mas, ao mudar para ConcurrentHashMap, fiquei várias vezes com problemas por causa de NullPointerExceptions.

Marcel
fonte
1
Eu acho que é uma inconsistência extremamente irritante. O EnumMap também não permite nulo. Obviamente, não há limitação técnica que desaprove chaves nulas. para um Mapa <K, V>, simplesmente um campo do tipo V fornecerá suporte para chaves nulas (provavelmente outro campo booleano se você quiser diferenciar entre valor nulo e nenhum valor).
RAY
6
Uma pergunta melhor é "por que o HashMap permite uma chave nula e valores nulos?". Ou, possivelmente, "por que o Java permite que o nulo habite todos os tipos?" Ou mesmo "por que o Java tem nulos?".
Jed Wesley-Smith

Respostas:

220

Do autor de ConcurrentHashMapsi mesmo (Doug Lea) :

O principal motivo pelo qual nulos não são permitidos no ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) é que ambiguidades que podem ser apenas toleráveis ​​em mapas não simultâneos não podem ser acomodadas. A principal é que, se map.get(key)retornar null, você não poderá detectar se nulla chave é mapeada explicitamente para a tecla vs não está mapeada. Em um mapa não concorrente, você pode verificar isso via map.contains(key), mas em um concorrente, o mapa pode ter mudado entre as chamadas.

Bruno
fonte
7
Obrigado, mas que tal ter nulo como chave?
AmitW
2
por que não usar Optionals como valores internamente
Benez
2
O @benez Optionalé um recurso do Java 8, que não estava disponível na época (Java 5). Você poderia usar Optionals agora, de fato.
Bruno
@ AmmitW, acho que o ans é o mesmo, ou seja, ambiguidades. Por exemplo, suponha que um encadeamento crie uma chave como nula e armazene um valor nela. Em seguida, outro segmento alterou outra chave para nulo. Quando o segundo thread tentar adicionar um novo valor, ele será substituído. Se o segundo thread tentar obter valor, ele obterá o valor para uma chave diferente, a modificada pela primeira. Essa situação deve ser evitada.
Dexter
44

Acredito que é, pelo menos em parte, permitir que você combine containsKeye faça getuma única ligação. Se o mapa puder conter nulos, não há como saber se getestá retornando um nulo porque não havia chave para esse valor ou apenas porque o valor era nulo.

Por que isso é um problema? Porque não há maneira segura de fazer isso sozinho. Pegue o seguinte código:

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

Como mé um mapa simultâneo, a chave k pode ser excluída entre as chamadas containsKeye get, fazendo com que esse trecho retorne um nulo que nunca estava na tabela, e não o desejado KeyNotPresentException.

Normalmente você resolveria isso sincronizando, mas com um mapa simultâneo que, obviamente, não funcionaria. Daí a assinatura paraget teve que mudar, e a única maneira de fazer isso de maneira compatível com versões anteriores era impedir o usuário inserir valores nulos em primeiro lugar e continuar usando-o como um espaço reservado para "chave não encontrada".

Alice Purcell
fonte
Você pode fazer map.getOrDefault(key, NULL_MARKER). Se for null, o valor foi null. Se retornar NULL_MARKER, o valor não estava presente.
Oliv
@ Oliv Apenas a partir do Java 8. Além disso, pode não haver um marcador nulo sensível para esse tipo.
Alice Purcell
@AlicePurcell ", mas com um mapa simultâneo que, obviamente, não funcionará" - por que eu posso sincronizar na versão simultânea da mesma forma - então me pergunto por que não funcionará. você pode elaborar isso?
samshers 01/09/19
@samshers Nenhuma operação em um mapa simultâneo é sincronizada; portanto, você precisa sincronizar externamente todas as chamadas. Nesse ponto, além de perder todo o benefício de ter um mapa simultâneo, você deixou uma armadilha para futuros mantenedores que naturalmente esperaria poder acessar com segurança um mapa simultâneo sem sincronizar.
Alice Purcell #
@AlicePurcell, great. Embora tecnicamente possível, isso definitivamente será um pesadelo de manutenção e não será esperado por usuários posteriores que eles tenham que sincronizar na versão simultânea.
samshers 10/09/19
4

Josh Bloch projetou HashMap; Doug Lea projetou ConcurrentHashMap. Espero que não seja difamatório. Na verdade, acho que o problema é que os nulos geralmente exigem quebra automática para que o nulo real possa significar não inicializado. Se o código do cliente exigir nulos, ele poderá pagar o custo (reconhecidamente pequeno) de agrupar os nulos.

Tom Hawtin - linha de orientação
fonte
2

Você não pode sincronizar em um nulo.

Edit: Não é exatamente por isso que neste caso. Inicialmente, pensei que havia algo extravagante em bloquear as atualizações simultâneas ou usar o monitor de objetos para detectar se algo foi modificado, mas, ao examinar o código - fonte , parece que eu estava errado - eles bloqueiam usando um "segmento" baseado em um máscara de bit do hash.

Nesse caso, suspeito que eles fizeram isso para copiar o Hashtable, e suspeito que o Hashtable fez isso porque no mundo do banco de dados relacional, null! = Null, portanto, usar um null como chave não tem significado.

Paul Tomblin
fonte
Hã? Não há sincronização feita nas chaves e valores de um mapa. Isso nao faria sentido.
Tobias Müller
Existem outros tipos de bloqueio realizados. É isso que o torna "Concorrente". Para fazer isso, ele precisa de um objeto para se manter.
21139 Paul Tomblin
2
Por que não existe um objeto especial interno que possa ser usado para sincronizar valores nulos? por exemplo "objeto privado NULL = new Object ();". Eu acho que eu já vi isso antes ...
Marcel
Que outros tipos de bloqueio você quer dizer?
Tobias Müller
Na verdade, agora que olho para o código-fonte gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/… , estou tendo sérias dúvidas sobre isso. Parece que usa bloqueio de segmento, não bloqueando itens individuais.
Paul Tomblin 30/03/09
0

ConcurrentHashMap é seguro para threads. Acredito que não permitir chaves e valores nulos fazia parte de garantir que seja seguro para threads.

Kevin Crowell
fonte
0

Acho que o seguinte trecho da documentação da API fornece uma boa dica: "Essa classe é totalmente interoperável com o Hashtable em programas que dependem da segurança do encadeamento, mas não dos detalhes da sincronização".

Eles provavelmente só queriam tornar ConcurrentHashMaptotalmente compatíveis / intercambiáveis ​​para Hashtable. E como Hashtablenão permite chaves e valores nulos.

Tobias Müller
fonte
2
E por que o Hashtable não suporta nulo?
Marcel
Observando seu código, não vejo uma razão óbvia para o Hashtable não permitir valores nulos. Talvez tenha sido apenas uma decisão da API quando a classe foi criada ?! O HashMap possui um tratamento especial para o caso nulo internamente, que o Hashtable não possui. (É sempre lança NullPointerException.)
Tobias Müller
-2

Eu não acho que não permitir valor nulo é uma opção correta. Em muitos casos, queremos colocar uma chave com valor nulo no mapa atual. No entanto, usando ConcurrentHashMap, não podemos fazer isso. Sugiro que a próxima versão do JDK possa suportar isso.

yinhaomin
fonte
1
Você já pensou em concorrer a Javachampion?
BlackBishop
Use Opcional se desejar um comportamento semelhante a nulo em suas chaves.
Alice Purcell