Seu idioma está seguro se e somente se a referência ao HashMap
for publicada com segurança . Em vez de qualquer coisa relacionada aos internos HashMap
, a publicação segura lida com como o thread de construção torna a referência ao mapa visível para outros threads.
Basicamente, a única corrida possível aqui é entre a construção do HashMap
e qualquer thread de leitura que possa acessá-lo antes de ser totalmente construído. A maior parte da discussão é sobre o que acontece com o estado do objeto do mapa, mas isso é irrelevante, pois você nunca o modifica - portanto, a única parte interessante é como a HashMap
referência é publicada.
Por exemplo, imagine que você publique o mapa assim:
class SomeClass {
public static HashMap<Object, Object> MAP;
public synchronized static setMap(HashMap<Object, Object> m) {
MAP = m;
}
}
... e em algum momento setMap()
é chamado com um mapa, e outros threads estão usando SomeClass.MAP
para acessar o mapa e verifique se há nulos como este:
HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
.. use the map
} else {
.. some default behavior
}
Isso não é seguro , embora provavelmente pareça ser. O problema é que não há relação de antes do acontecimento entre o conjunto de SomeObject.MAP
e a leitura subsequente em outro encadeamento, portanto, o encadeamento de leitura fica livre para ver um mapa parcialmente construído. Isso pode praticamente fazer qualquer coisa e, mesmo na prática, faz coisas como colocar o thread de leitura em um loop infinito .
Para publicar o mapa com segurança, você precisa estabelecer uma relação de antes do acontecimento entre a escrita da referência à HashMap
(ou seja, a publicação ) e os leitores subsequentes dessa referência (ou seja, o consumo). Convenientemente, existem apenas algumas maneiras fáceis de lembrar de conseguir isso [1] :
- Troque a referência por um campo bloqueado corretamente ( JLS 17.4.5 )
- Use o inicializador estático para fazer os armazenamentos de inicialização ( JLS 12.4 )
- Troque a referência por meio de um campo volátil ( JLS 17.4.5 ), ou como conseqüência desta regra, pelas classes AtomicX
- Inicialize o valor em um campo final ( JLS 17.5 ).
Os mais interessantes para o seu cenário são (2), (3) e (4). Em particular, (3) se aplica diretamente ao código que tenho acima: se você transformar a declaração de MAP
para:
public static volatile HashMap<Object, Object> MAP;
então tudo é kosher: os leitores que veem um valor não nulo necessariamente têm um relacionamento de antes da loja MAP
e, portanto, veem todas as lojas associadas à inicialização do mapa.
Os outros métodos alteram a semântica do seu método, pois ambos (2) (usando o inicializador estático) e (4) (usando final ) implicam que você não pode definir MAP
dinamicamente no tempo de execução. Se você não precisar fazer isso, basta declarar MAP
como static final HashMap<>
e você terá uma publicação segura.
Na prática, as regras são simples para acesso seguro a "objetos nunca modificados":
Se você estiver publicando um objeto que não é inerentemente imutável (como em todos os campos declarados final
) e:
- Você já pode criar o objeto que será atribuído no momento da declaração a : basta usar um
final
campo (inclusive static final
para membros estáticos).
- Você deseja atribuir o objeto posteriormente, depois que a referência já estiver visível: use um campo volátil b .
É isso aí!
Na prática, é muito eficiente. O uso de um static final
campo, por exemplo, permite que a JVM assuma o valor inalterado durante a vida útil do programa e o otimize fortemente. O uso de um final
campo membro permite que a maioria das arquiteturas leia o campo de maneira equivalente a uma leitura normal do campo e não inibe outras otimizações c .
Finalmente, o uso de volatile
tem algum impacto: nenhuma barreira de hardware é necessária em muitas arquiteturas (como x86, especificamente aquelas que não permitem que as leituras passem por leituras), mas algumas otimizações e reordenações podem não ocorrer no tempo de compilação - mas isso efeito é geralmente pequeno. Em troca, você realmente obtém mais do que solicitou - não apenas pode publicar com segurança um HashMap
, mas também pode armazenar tantos outros HashMap
s não modificados quanto desejar na mesma referência e ter certeza de que todos os leitores verão um mapa publicado com segurança .
Para mais detalhes, consulte Shipilev ou esta FAQ de Manson e Goetz .
[1] Citando diretamente do shipilev .
a Isso parece complicado, mas o que quero dizer é que você pode atribuir a referência no momento da construção - no ponto da declaração ou no construtor (campos de membros) ou no inicializador estático (campos estáticos).
b Opcionalmente, você pode usar um synchronized
método para obter / definir, ou um AtomicReference
ou algo assim, mas estamos falando do trabalho mínimo que você pode fazer.
c Algumas arquiteturas com modelos de memória muito fracos (estou olhando para você , Alpha) podem exigir algum tipo de barreira de leitura antes de uma final
leitura - mas elas são muito raras hoje em dia.