CodeGym/Cursos Java/Módulo 3/Operações atômicas em Java

Operações atômicas em Java

Disponível

Pré-requisitos para o surgimento de operações atômicas

Vamos dar uma olhada neste exemplo para ajudá-lo a entender como as operações atômicas funcionam:

public class Counter {
    int count;

    public void increment() {
        count++;
    }
}

Quando temos um thread, tudo funciona muito bem, mas se adicionarmos multithreading, obtemos resultados errados, e tudo porque a operação de incremento não é uma operação, mas três: uma solicitação para obter o valor atualcontar, em seguida, incremente-o em 1 e escreva novamente paracontar.

E quando dois threads desejam incrementar uma variável, você provavelmente perderá dados. Ou seja, ambas as threads recebem 100, como resultado, ambas escreverão 101 em vez do valor esperado de 102.

E como resolver? Você precisa usar fechaduras. A palavra-chave sincronizada ajuda a resolver esse problema, pois seu uso garante que uma thread acessará o método por vez.

public class SynchronizedCounterWithLock {
    private volatile int count;

    public synchronized void increment() {
        count++;
    }
}

Além disso, você precisa adicionar a palavra-chave volátil , que garante a visibilidade correta das referências entre os threads. Nós revisamos seu trabalho acima.

Mas ainda há desvantagens. O maior deles é o desempenho, naquele momento em que muitos encadeamentos estão tentando adquirir um bloqueio e um obtém uma oportunidade de gravação, o restante dos encadeamentos será bloqueado ou suspenso até que o encadeamento seja liberado.

Todos esses processos, bloqueio, mudança para outro status são muito caros para o desempenho do sistema.

operações atômicas

O algoritmo usa instruções de máquina de baixo nível, como compare-and-swap (CAS, compare-and-swap, que garante a integridade dos dados e já existe uma grande quantidade de pesquisas sobre eles).

Uma operação CAS típica opera em três operandos:

  • Espaço de memória para trabalho (M)
  • Valor esperado existente (A) de uma variável
  • Novo valor (B) a definir

O CAS atualiza atomicamente M para B, mas somente se o valor de M for o mesmo que A, caso contrário, nenhuma ação será executada.

No primeiro e no segundo caso, será devolvido o valor de M. Isso permite combinar três etapas, ou seja, obter o valor, comparar o valor e atualizá-lo. E tudo se transforma em uma operação no nível da máquina.

No momento em que uma aplicação multi-thread acessa uma variável e tenta atualizá-la e o CAS é aplicado, então uma das threads irá obtê-la e poderá atualizá-la. Mas, ao contrário dos bloqueios, outros encadeamentos simplesmente obterão erros por não serem capazes de atualizar o valor. Em seguida, eles passarão a trabalhar mais, e a troca é totalmente excluída nesse tipo de trabalho.

Nesse caso, a lógica se torna mais difícil devido ao fato de termos que lidar com a situação em que a operação CAS não funcionou com sucesso. Vamos apenas modelar o código para que ele não prossiga até que a operação seja bem-sucedida.

Introdução aos tipos atômicos

Você já se deparou com uma situação em que precisa configurar a sincronização para a variável mais simples do tipo int ?

A primeira maneira que já cobrimos é usar volátil + sincronizado . Mas também há aulas especiais de Atomic*.

Se usarmos o CAS, as operações funcionarão mais rapidamente em comparação com o primeiro método. Além disso, temos métodos especiais e muito convenientes para adicionar um valor e operações de incremento e decremento.

AtomicBoolean , AtomicInteger , AtomicLong , AtomicIntegerArray , AtomicLongArray são classes nas quais as operações são atômicas. A seguir analisaremos o trabalho com eles.

AtomicInteger

A classe AtomicInteger fornece operações em um valor int que pode ser lido e gravado atomicamente, além de fornecer operações atômicas estendidas.

Possui métodos get e set que funcionam como variáveis ​​de leitura e escrita.

Ou seja, “acontece-antes” com qualquer recebimento subsequente da mesma variável de que falamos anteriormente. O método compareAndSet atômico também possui esses recursos de consistência de memória.

Todas as operações que retornam um novo valor são executadas atomicamente:

int addAndGet (int delta) Adiciona um valor específico ao valor atual.
boolean compareAndSet (int esperado, int de atualização) Define o valor para o valor atualizado fornecido se o valor atual corresponder ao valor esperado.
int decrementAndGet() Diminui o valor atual em um.
int getAndAdd(int delta) Adiciona o valor fornecido ao valor atual.
int getAndDecrement() Diminui o valor atual em um.
int getAndIncrement() Aumenta o valor atual em um.
int getAndSet(int newValue) Define o valor fornecido e retorna o valor antigo.
int incrementAndGet() Aumenta o valor atual em um.
lazySet(int newValue) Por fim, defina o valor fornecido.
booleano fracoCompareAndSet(esperado, atualização int) Define o valor para o valor atualizado fornecido se o valor atual corresponder ao valor esperado.

Exemplo:

ExecutorService executor = Executors.newFixedThreadPool(5);
IntStream.range(0, 50).forEach(i -> executor.submit(atomicInteger::incrementAndGet));
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);

System.out.println(atomicInteger.get()); // prints 50
1
Tarefa
Módulo 3,  nível 19lição 1
Bloqueado
Earn a Million!
task4201
1
Tarefa
Módulo 3,  nível 19lição 1
Bloqueado
Early Bird Gets the Worm
task4202
Comentários
  • Populares
  • Novas
  • Antigas
Você precisa acessar para deixar um comentário
Esta página ainda não tem nenhum comentário