AtomicInteger lazySet vs. set

116

Qual é a diferença entre os métodos lazySete setde AtomicInteger? A documentação não tem muito a dizer sobre lazySet:

Eventualmente define o valor fornecido.

Parece que o valor armazenado não será definido imediatamente para o valor desejado, mas será programado para ser definido em algum momento no futuro. Mas, qual é o uso prático desse método? Algum exemplo?

Cheok Yan Cheng
fonte

Respostas:

114

Citado diretamente de "JDK-6275329: Adicionar métodos lazySet às classes atômicas" :

Como provavelmente o último pequeno acompanhamento JSR166 do Mustang, adicionamos um método "lazySet" às classes Atomic (AtomicInteger, AtomicReference, etc). Este é um método de nicho que às vezes é útil ao ajustar o código usando estruturas de dados sem bloqueio. A semântica é que a gravação é garantida para não ser reordenada com qualquer gravação anterior, mas pode ser reordenada com operações subsequentes (ou equivalentemente, pode não ser visível para outros threads) até que alguma outra gravação volátil ou ação de sincronização ocorra).

O principal caso de uso é para anular campos de nós em estruturas de dados sem bloqueio apenas para evitar a retenção de lixo de longo prazo; ele se aplica quando é inofensivo se outros threads veem valores não nulos por um tempo, mas você gostaria de garantir que as estruturas sejam eventualmente GCable. Nesses casos, você pode obter um melhor desempenho evitando os custos da gravação volátil nula. Existem alguns outros casos de uso ao longo dessas linhas para atômicos não baseados em referência também, portanto, o método é compatível com todas as classes AtomicX.

Para as pessoas que gostam de pensar nessas operações em termos de barreiras no nível da máquina em multiprocessadores comuns, o lazySet fornece uma barreira loja-loja anterior (que pode ser autônoma ou muito barata nas plataformas atuais), mas sem barreira de armazenamento (que geralmente é a parte cara de uma gravação volátil).

bocejar
fonte
14
Alguém poderia emburrecer isso para o resto de nós? :(
Gaurav
14
Lazy é a versão não volátil (por exemplo, a mudança de estado não é garantida como visível para todos os threads que possuem o Atomic*escopo).
bocejo
63
o que eu não entendo é por que javadoc é tão pobre sobre isso.
Felipe
8
Tenho certeza que eles eventualmente conseguirão mudar isso. Boom boom.
MMJZ
3
para aqueles que querem saber mais sobre a barreira de armazenamento / carga e por que a barreira de loja-loja é mais barata do que a barreira de carga de loja. Aqui está um artigo fácil de entender sobre isso. Mechanical-sympathy.blogspot.com/2011/07/…
Kin Cheung
15

lazySet pode ser usado para comunicação entre threads rmw, porque xchg é atômico, quanto à visibilidade, quando o processo de thread do gravador modifica a localização de uma linha de cache, o processador da thread do leitor o verá na próxima leitura, porque o protocolo de coerência do cache da CPU Intel garantirá LazySet funciona, mas a linha do cache será atualizada na próxima leitura, novamente, a CPU precisa ser moderna o suficiente.

http://sc.tamu.edu/systems/eos/nehalem.pdf Para Nehalem, que é uma plataforma de multiprocessador, os processadores têm a capacidade de "espionar" (espionar) o barramento de endereço para acessos de outros processadores à memória do sistema e para seus caches internos. Eles usam essa capacidade de espionagem para manter seus caches internos consistentes com a memória do sistema e com os caches em outros processadores interconectados. Se, por meio de espionagem, um processador detectar que outro processador pretende gravar em um local da memória que atualmente armazenou em cache no estado compartilhado, o processador de espionagem invalidará seu bloco de cache, forçando-o a realizar um preenchimento de linha de cache na próxima vez que acessar o mesmo local de memória .

oracle hotspot jdk para arquitetura de cpu x86->

lazySet == unsafe.putOrderedLong == xchg rw (instrução asm que serve como uma barreira suave que custa 20 ciclos na CPU Intel Nehelem)

em x86 (x86_64), tal barreira é muito mais barata em termos de desempenho do que volátil ou AtomicLong getAndAdd,

Em um produtor, um cenário de fila de consumidor, a barreira suave xchg pode forçar a linha de códigos antes do lazySet (sequência + 1) para que o encadeamento do produtor aconteça ANTES de qualquer código de encadeamento do consumidor que consumirá (trabalhará) os novos dados, é claro o thread do consumidor precisará verificar atomicamente se a sequência do produtor foi incrementada por exatamente um usando um compareAndSet (sequência, sequência + 1).

Rastreei o código-fonte do Hotspot para encontrar o mapeamento exato do lazySet para o código cpp: http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/share/vm/prims/unsafe. cpp Unsafe_setOrderedLong -> definição SET_FIELD_VOLATILE -> OrderAccess: release_store_fence. Para x86_64, OrderAccess: release_store_fence é definido como usando a instrução xchg.

Você pode ver como ele está exatamente definido no jdk7 (doug lea está trabalhando em algumas coisas novas para o JDK 8): http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/4fc084dac61e/src/os_cpu/ linux_x86 / vm / orderAccess_linux_x86.inline.hpp

você também pode usar o hdis para desmontar o assembly do código lazySet em ação.

Há outra questão relacionada: precisamos de mfence ao usar xchg

costelinha de porco
fonte
5
É difícil entender o que você quer chegar aqui. Você pode esclarecer seu ponto?
Paul Bellora
3
"lazySet == unsafe.putOrderedLong == xchg rw (instrução asm que serve como uma barreira suave custando 20 ciclos na cpu intel nehelem) em x86 (x86_64) tal barreira é muito mais barata em termos de desempenho do que volátil ou AtomicLong getAndAdd" -> Isso não é verdade até onde sei. lazySet / putOrdered é um MOV para um endereço, que é o motivo pelo qual o livro de receitas JMM o descreve como um autônomo no x86.
Nitsan Wakart
11

Uma discussão mais ampla sobre as origens e a utilidade de lazySet e putOrdered subjacente pode ser encontrada aqui: http://psy-lob-saw.blogspot.co.uk/2012/12/atomiclazyset-is-performance-win-for.html

Para resumir: lazySet é uma gravação fraca e volátil, no sentido de que atua como um armazenamento de armazenamento e não um limite de carregamento de armazenamento. Isso se resume a lazySet sendo JIT compilado para uma instrução MOV que não pode ser reordenada pelo compilador, em vez da instrução significativamente mais cara usada para um conjunto volátil.

Ao ler o valor você sempre acaba fazendo uma leitura volátil (com um Atomic * .get () em qualquer caso).

O lazySet oferece a um único gravador um mecanismo de gravação volátil consistente, ou seja, é perfeitamente legítimo para um único gravador usar o lazySet para incrementar um contador, vários threads incrementando o mesmo contador terão que resolver as gravações concorrentes usando CAS, que é exatamente o que acontece em as capas do Atomic * para incAndGet.

Nitsan Wakart
fonte
exatamente, por que não podemos dizer que esta é uma StoreStorebarreira simples , mas não uma StoreLoad?
Eugene
8

Do resumo do pacote atômico simultâneo

lazySet tem os efeitos de memória de escrever (atribuir) uma variável volátil, exceto que permite reordenamentos com ações de memória subsequentes (mas não anteriores) que não impõem restrições de reordenamento com gravações não voláteis comuns. Entre outros contextos de uso, lazySet pode ser aplicado ao anular, para fins de coleta de lixo, uma referência que nunca é acessada novamente.

Se você está curioso sobre o lazySet, você também deve a si mesmo outras explicações

Os efeitos de memória para acessos e atualizações de atômicas geralmente seguem as regras para voláteis, conforme declarado na seção 17.4 da Especificação da linguagem Java ™.

get tem os efeitos de memória de ler uma variável volátil.

set tem os efeitos de memória de escrever (atribuir) uma variável volátil.

lazySet tem os efeitos de memória de escrever (atribuir) uma variável volátil, exceto que permite reordenamentos com ações de memória subsequentes (mas não anteriores) que não impõem restrições de reordenamento com gravações não voláteis comuns. Entre outros contextos de uso, lazySet pode ser aplicado ao anular, para fins de coleta de lixo, uma referência que nunca é acessada novamente.

fracoCompareAndSet lê atomicamente e grava condicionalmente uma variável, mas não cria nenhuma ordem acontece antes, portanto, não oferece garantias em relação a leituras e gravações anteriores ou subsequentes de quaisquer variáveis ​​que não sejam o destino do fracoCompareAndSet.

compareAndSet e todas as outras operações de leitura e atualização, como getAndIncrement, têm os efeitos de memória de leitura e gravação de variáveis ​​voláteis.

Ajeet Ganga
fonte
4

Aqui está o meu entendimento, corrija-me se eu estiver errado: Você pode pensar em lazySet()como "semi" volátil: é basicamente uma variável não volátil em termos de leitura por outros tópicos, ou seja, o valor definido por lazySet pode não ser visível para outros tópicos. Mas ele se torna volátil quando ocorre outra operação de gravação (pode ser de outros threads). O único impacto do lazySet que posso imaginar é compareAndSet. Portanto, se você usar lazySet(), get()de outros threads ainda pode obter o valor antigo, mas compareAndSet()sempre terá o novo valor, pois é uma operação de gravação.

Jyluo
fonte
1
você não quer dizer compareAndSet?
Dave Moten
2

Re: tentativa de emburrecer -

Você pode pensar nisso como uma forma de tratar um campo volátil como se não fosse volátil para uma operação de armazenamento em particular (por exemplo: ref = null;).

Isso não é perfeitamente preciso, mas deve ser o suficiente para que você possa tomar uma decisão entre "OK, eu realmente não me importo" e "Hmm, deixe-me pensar um pouco sobre isso".

Paul Mclachlan
fonte