Atualizado: 02/11/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Atomicidade e Atomics: Segurança entre Threads sem Bloqueios em Java

Em aplicações multithread, um dos problemas mais comuns é a condição de corrida (race condition), que ocorre quando vários threads tentam ler e modificar a mesma variável ao mesmo tempo. Isso leva a resultados imprevisíveis e erros difíceis de reproduzir.

Considere o exemplo clássico de um contador compartilhado:

public class Program {

    public static void main(String[] args) throws InterruptedException {

        int numThreads = 1000;
        int incrementsPerThread = 1000;

        Counter counter = new Counter();

        Thread[] threads = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    counter.increment();
                }
            });
            threads[i].start();
        }

        Thread.sleep(1000);

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("Counter: " + counter.getCounter());
    }
}

class Counter {

    private volatile long counter = 0;

    long getCounter() { return counter; }

    void increment() { counter++; }  // NÃO É ATOMÁRICO!
}

Mesmo com o uso de volatile, o resultado será inconsistente — algo como:

Counter: 944695

ou

Counter: 870973

O valor nunca será exatamente 1.000.000, como esperado. Isso acontece porque a operação counter++ não é atômica: ela é composta de três etapas — leitura, incremento e escrita — que podem ser intercaladas por outros threads.


Por que não usar bloqueios (synchronized / ReentrantLock)?

Uma solução seria sincronizar o método increment(), por exemplo com synchronized. Mas isso introduz bloqueios, e enquanto um thread segura o lock, os outros ficam bloqueados, esperando. Em sistemas com alta concorrência, isso gera espera, trocas de contexto e queda de desempenho.


Solução eficiente: pacote java.util.concurrent.atomic

Para resolver esse tipo de problema sem bloquear threads, o Java oferece o pacote java.util.concurrent.atomic. Ele contém classes como:

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
  • AtomicReference

Essas classes usam instruções atômicas de hardware (como compare-and-swap) para garantir consistência sem bloqueios.

As operações são thread-safe e não bloqueantes, tornando o código muito mais eficiente.


Exemplo com AtomicLong

import java.util.concurrent.atomic.AtomicLong;

public class Program {

    public static void main(String[] args) throws InterruptedException {

        int numThreads = 1000;
        int incrementsPerThread = 1000;

        Counter counter = new Counter();

        Thread[] threads = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    counter.increment();
                }
            });
            threads[i].start();
        }

        Thread.sleep(1000);

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("Counter: " + counter.getCounter());
    }
}

class Counter {

    private AtomicLong counter = new AtomicLong(0);

    long getCounter() { return counter.get(); }

    void increment() { counter.getAndIncrement(); }  // OPERAÇÃO ATÔMICA!
}

Saída determinística:

Counter: 1000000

Agora o resultado está correto, pois getAndIncrement() é uma operação atômica — executada integralmente, sem interferência de outros threads.


Principais métodos de AtomicLong

MétodoDescrição
get()Retorna o valor atual
set(long newValue)Define um novo valor
incrementAndGet()Incrementa e retorna o novo valor (++i)
getAndIncrement()Incrementa e retorna o valor anterior (i++)
decrementAndGet()Decrementa e retorna o novo valor (--i)
getAndDecrement()Decrementa e retorna o valor anterior (i--)
addAndGet(long delta)Soma um valor e retorna o novo
getAndAdd(long delta)Soma um valor e retorna o antigo
compareAndSet(long expect, long update)Altera o valor apenas se ele for igual ao esperado (CAS)

Essas operações são lock-free, ou seja, garantem integridade sem usar synchronized.


LongAdder: desempenho otimizado para alta concorrência

Em ambientes com muitos threads atualizando o mesmo contador, até as operações atômicas podem gerar contenção (disputa pelo mesmo valor). Para isso, o Java introduziu o LongAdder, que distribui a carga entre múltiplas células internas.

Cada thread atualiza sua própria célula, e o método sum() combina os valores no final. Isso reduz drasticamente a contenção e melhora o desempenho em sistemas de alta concorrência.

import java.util.concurrent.atomic.LongAdder;

public class Program {

    public static void main(String[] args) throws InterruptedException {

        int numThreads = 1000;
        int incrementsPerThread = 1000;

        Counter counter = new Counter();

        Thread[] threads = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    counter.increment();
                }
            });
            threads[i].start();
        }

        Thread.sleep(1000);

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("Counter: " + counter.getCounter());
    }
}

class Counter {

    private LongAdder counter = new LongAdder();

    long getCounter() { return counter.longValue(); }

    void increment() { counter.increment(); }  // cada thread atualiza sua célula
}

Saída:

Counter: 1000000

O LongAdder é ideal para contadores de métricas e estatísticas, onde não é necessário conhecer o valor exato a cada momento, apenas o total agregado ao final.


Resumo

ClasseCaracterísticasUso típico
volatileGarante visibilidade, mas não atomicidadeSinalizadores simples (running, active)
AtomicLongOpera de forma atômica e thread-safe, mas pode sofrer contençãoContadores moderados
LongAdderDivide o estado entre múltiplas células para reduzir contençãoContadores de alta concorrência

Conclusão

  • As operações ++ e -- não são atômicas — mesmo com volatile.
  • Use AtomicLong para contadores simples, quando muitos threads compartilham o mesmo valor.
  • Use LongAdder quando há muitos threads atualizando o mesmo contador simultaneamente.
  • Essas soluções eliminam a necessidade de bloqueios (synchronized), mantendo o código seguro e escalável.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com