Existem dois usos principais de AtomicInteger
:
Como um contador atômico ( incrementAndGet()
, etc) que pode ser usado por muitos threads simultaneamente
Como uma primitiva que suporta instruções de comparação e troca ( compareAndSet()
) para implementar algoritmos sem bloqueio.
Aqui está um exemplo de gerador de números aleatórios sem bloqueio da Java Concurrency Na Prática de Brian Göetz :
public class AtomicPseudoRandom extends PseudoRandom {
private AtomicInteger seed;
AtomicPseudoRandom(int seed) {
this.seed = new AtomicInteger(seed);
}
public int nextInt(int n) {
while (true) {
int s = seed.get();
int nextSeed = calculateNext(s);
if (seed.compareAndSet(s, nextSeed)) {
int remainder = s % n;
return remainder > 0 ? remainder : remainder + n;
}
}
}
...
}
Como você pode ver, basicamente funciona quase da mesma maneira que incrementAndGet()
, mas executa cálculos arbitrários ( calculateNext()
) em vez de incrementar (e processa o resultado antes do retorno).
read
ewrite that value + 1
, isso será detectado em vez de substituir a atualização antiga (evitando o problema de "atualização perdida"). Este é realmente um caso especial decompareAndSet
- se o valor antigo era2
, a classe realmente chamacompareAndSet(2, 3)
-, se outro encadeamento modificou o valor nesse meio tempo, o método de incremento é reiniciado efetivamente desde o início.O exemplo mais simples e absoluto que consigo pensar é tornar o incremento uma operação atômica.
Com entradas padrão:
Com AtomicInteger:
A última é uma maneira muito simples de executar efeitos simples de mutações (especialmente contagem ou indexação exclusiva), sem ter que recorrer à sincronização de todo acesso.
Uma lógica mais complexa, livre de sincronização, pode ser empregada usando
compareAndSet()
como um tipo de bloqueio otimista - obtenha o valor atual, calcule o resultado com base nisso, defina esse resultado se o valor ainda for a entrada usada para o cálculo, mas comece novamente - mas o exemplos de contagem são muito úteis, e eu usarei frequentementeAtomicIntegers
para contar e geradores exclusivos para toda a VM, se houver alguma sugestão de vários encadeamentos envolvidos, porque são tão fáceis de trabalhar, que eu quase consideraria otimização prematura usarints
.Embora você quase sempre possa obter as mesmas garantias de sincronização
ints
esynchronized
declarações apropriadas , a vantagemAtomicInteger
é que a segurança do encadeamento é incorporada ao próprio objeto real, em vez de você precisar se preocupar com as possíveis intercalações e monitores mantidos, de todos os métodos isso acontece para acessar oint
valor. É muito mais difícil violar acidentalmente a segurança da thread ao ligar dogetAndIncrement()
que ao retornari++
e lembrar (ou não) de adquirir o conjunto correto de monitores com antecedência.fonte
Se você observar os métodos do AtomicInteger, notará que eles tendem a corresponder a operações comuns em ints. Por exemplo:
é a versão segura para threads:
Os métodos mapeiam assim:
++i
isi.incrementAndGet()
i++
isi.getAndIncrement()
--i
isi.decrementAndGet()
i--
isi.getAndDecrement()
i = x
isi.set(x)
x = i
isx = i.get()
Existem outros métodos de conveniência, como
compareAndSet
ouaddAndGet
fonte
O principal uso
AtomicInteger
é quando você está em um contexto multithread e precisa executar operações seguras de encadeamento em um número inteiro sem usarsynchronized
. A atribuição e recuperação no tipo primitivoint
já são atômicas, masAtomicInteger
vêm com muitas operações que não são atômicasint
.Os mais simples são os
getAndXXX
ouxXXAndGet
. Por exemplo,getAndIncrement()
é um equivalente atômico aoi++
qual não é atômico, porque na verdade é um atalho para três operações: recuperação, adição e atribuição.compareAndSet
é muito útil para implementar semáforos, fechaduras, travas etc.Usar o
AtomicInteger
é mais rápido e legível do que executar o mesmo usando a sincronização.Um teste simples:
No meu PC com Java 1.6, o teste atômico é executado em 3 segundos, enquanto o sincronizado é executado em cerca de 5,5 segundos. O problema aqui é que a operação para sincronizar (
notAtomic++
) é realmente curta. Portanto, o custo da sincronização é realmente importante em comparação com a operação.Além da atomicidade, o AtomicInteger pode ser usado como uma versão mutável,
Integer
por exemplo, emMap
s como valores.fonte
AtomicInteger
como chave de mapa, porque usa aequals()
implementação padrão , que quase certamente não é o que você esperaria que a semântica fosse se fosse usada em um mapa.Por exemplo, eu tenho uma biblioteca que gera instâncias de alguma classe. Cada uma dessas instâncias deve ter um ID inteiro exclusivo, pois essas instâncias representam comandos sendo enviados para um servidor e cada comando deve ter um ID exclusivo. Como vários threads têm permissão para enviar comandos simultaneamente, eu uso um AtomicInteger para gerar esses IDs. Uma abordagem alternativa seria usar algum tipo de bloqueio e um número inteiro regular, mas isso é mais lento e menos elegante.
fonte
Como gabuzo disse, às vezes eu uso AtomicIntegers quando quero passar um int por referência. É uma classe interna que possui código específico da arquitetura, por isso é mais fácil e provavelmente mais otimizado do que qualquer MutableInteger que eu poderia codificar rapidamente. Dito isto, parece um abuso da classe.
fonte
No Java 8, as classes atômicas foram estendidas com duas funções interessantes:
Ambos estão usando o updateFunction para executar a atualização do valor atômico. A diferença é que o primeiro retorna o valor antigo e o segundo retorna o novo valor. O updateFunction pode ser implementado para executar operações mais complexas de "comparação e configuração" do que a operação padrão. Por exemplo, ele pode verificar se o contador atômico não fica abaixo de zero, normalmente exigiria sincronização e aqui o código é livre de bloqueios:
O código é obtido do Java Atomic Example .
fonte
Normalmente, uso o AtomicInteger quando preciso fornecer IDs para objetos que podem ser acessados ou criados a partir de vários threads, e geralmente o uso como um atributo estático na classe que eu acesso no construtor dos objetos.
fonte
Você pode implementar bloqueios sem bloqueio usando compareAndSwap (CAS) em números inteiros atômicos ou longos. O documento Memória transacional do software "Tl2" descreve isso:
O que está descrevendo é primeiro ler o número inteiro atômico. Divida isso em um bit de bloqueio ignorado e o número da versão. Tente gravar o CAS como o bit de bloqueio limpo com o número da versão atual no conjunto de bits de bloqueio e o próximo número da versão. Faça um loop até ter sucesso e você seja o segmento que possui o bloqueio. Desbloqueie definindo o número da versão atual com o bit de bloqueio limpo. O documento descreve o uso dos números de versão nos bloqueios para coordenar que os segmentos tenham um conjunto consistente de leituras quando escrevem.
Este artigo descreve que os processadores têm suporte de hardware para operações de comparação e troca, tornando o processo muito eficiente. Alega também:
fonte
A chave é que eles permitem acesso e modificação simultâneos com segurança. Eles são comumente usados como contadores em um ambiente multithread - antes da introdução, essa deveria ser uma classe escrita pelo usuário que agrupava os vários métodos em blocos sincronizados.
fonte
Usei o AtomicInteger para resolver o problema do jantar filósofo.
Na minha solução, as instâncias AtomicInteger foram usadas para representar os garfos, são necessárias duas por filósofo. Cada filósofo é identificado como um número inteiro, de 1 a 5. Quando um garfo é usado por um filósofo, o AtomicInteger mantém o valor do filósofo, de 1 a 5, caso contrário, o garfo não está sendo usado, portanto o valor do AtomicInteger é -1 .
O AtomicInteger permite verificar se um garfo está livre, valor == - 1, e defina-o como o proprietário do garfo, se estiver livre, em uma operação atômica. Veja o código abaixo.
Como o método compareAndSet não bloqueia, ele deve aumentar a taxa de transferência, mais trabalho realizado. Como você deve saber, o problema do Dining Philosophers é usado quando o acesso controlado aos recursos é necessário, ou seja, garfos, como um processo precisa de recursos para continuar trabalhando.
fonte
Exemplo simples para a função compareAndSet ():
O impresso é: valor anterior: 0 O valor foi atualizado e é 6 Outro exemplo simples:
O impresso é: Valor anterior: 0 O valor não foi atualizado
fonte