Palavra-chave volatile em Java

A palavra-chave volatile em Java é usada para marcar uma variável como sendo armazenada na memória principal, em vez de apenas no cache da thread. Isso significa que todas as threads que acessarem a variável terão sempre o valor mais recente, evitando inconsistências quando a variável é acessada simultaneamente por várias threads.

O Problema do Cache nas Threads

Em Java, cada thread pode armazenar variáveis em cache para melhorar o desempenho. Isso pode causar problemas quando várias threads acessam e modificam uma variável compartilhada. A palavra-chave volatile garante que a variável seja lida e escrita diretamente na memória principal, garantindo visibilidade entre threads.

Sintaxe





public class ExemploVolatile {
    private volatile boolean ativo = true;
    
    public void executar() {
        while (ativo) {
            // Lógica do programa
        }
    }
    
    public void parar() {
        ativo = false; // Atualiza a variável
    }
}

Funcionamento do volatile

  1. Visibilidade entre Threads: Quando uma variável é marcada como volatile, as threads sempre lerão seu valor diretamente da memória principal e não de um cache local. Isso garante que mudanças feitas por uma thread estejam visíveis para outras threads.
  2. Sem Cache: O valor da variável volatile nunca será armazenado em cache pelas threads, o que evita que elas trabalhem com uma cópia desatualizada.

Exemplo de Uso





public class ExemploVolatile {
    private volatile boolean ativo = true;

    public void executar() {
        System.out.println("Thread iniciada...");
        while (ativo) {
            // Código de execução contínua
        }
        System.out.println("Thread encerrada.");
    }

    public void parar() {
        ativo = false; // Atualiza o valor, que será visível para todas as threads
    }

    public static void main(String[] args) {
        ExemploVolatile exemplo = new ExemploVolatile();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                exemplo.executar();
            }
        });

        t1.start();

        try {
            Thread.sleep(2000); // Aguarda 2 segundos antes de parar a execução
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        exemplo.parar(); // Modifica a variável para false, parando a execução da thread
    }
}

Neste exemplo:

  • A variável ativo é marcada como volatile para garantir que todas as threads tenham acesso ao valor mais atualizado.
  • Uma thread executa o método executar, que continua rodando enquanto a variável ativo for true.
  • Após 2 segundos, o método parar é chamado, o que define a variável ativo como false e faz a thread parar.

Limitações do volatile

  • Não Garante Sincronização Completa: O volatile apenas garante visibilidade entre threads, mas não é suficiente para operações atômicas ou para garantir exclusão mútua (mutex). Se várias threads precisarem modificar uma variável de forma concorrente, será necessário usar outras técnicas, como blocos sincronizados (synchronized).
  • Leitura e Escrita Simples: O volatile só é útil para operações simples de leitura e escrita. Para operações mais complexas, como incrementar uma variável, é melhor usar classes atômicas como AtomicInteger.

Resumo

  • volatile garante que uma variável seja sempre lida e escrita na memória principal, assegurando a visibilidade entre threads.
  • É útil em cenários onde várias threads precisam acessar e modificar o valor de uma variável de forma consistente.
  • Não substitui blocos sincronizados (synchronized) ou outras técnicas para garantir atomicidade em operações mais complexas.