O que são variáveis voláteis (volatile) e quando usá-las?
A palavra-chave volatile
em Java é usada para garantir que mudanças feitas em uma variável por uma thread sejam imediatamente visíveis para todas as outras threads. Em aplicações concorrentes, o uso de volatile
ajuda a evitar problemas de inconsistência de memória causados pelo cache das threads.
1. Problema sem volatile
Quando uma variável não é volatile
, cada thread pode manter uma cópia em cache dela, levando a comportamentos inesperados:
class SemVolatile {
private static boolean ativo = true;
public static void main(String[] args) {
new Thread(() -> {
while (ativo) {
// Loop infinito, pois a thread pode não ver a mudança de "ativo"
}
System.out.println("Thread encerrada");
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {}
ativo = false; // A mudança pode não ser percebida pela thread
System.out.println("Valor alterado para false");
}
}
Explicação: A thread principal altera
ativo = false
, mas a thread secundária pode não perceber essa mudança devido ao cache de CPU.
2. Solução com volatile
O uso de volatile
garante que todas as threads acessem sempre o valor mais recente da variável:
class ComVolatile {
private static volatile boolean ativo = true;
public static void main(String[] args) {
new Thread(() -> {
while (ativo) {
// Agora a mudança será percebida corretamente
}
System.out.println("Thread encerrada");
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {}
ativo = false;
System.out.println("Valor alterado para false");
}
}
Explicação: Agora, sempre que
ativo
for modificado, todas as threads verão o novo valor imediatamente.
3. Regras de Uso de volatile
- Garante visibilidade, mas não atomicidade:
volatile
não impede condições de corrida em operações compostas (ex:contador++
). - Ideal para flags de controle: Útil para variáveis de controle de loop, como
ativo = false
. - Não substitui
synchronized
ouLock
: Se houver mais de um passo na modificação da variável, usesynchronized
ouReentrantLock
.
4. Quando NÃO Usar volatile
Se a variável precisa de operações atômicas (exemplo: incremento x++
), volatile
não é suficiente. Para isso, use AtomicInteger
:
import java.util.concurrent.atomic.AtomicInteger;
public class ExemploAtomic {
private static AtomicInteger contador = new AtomicInteger(0);
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
contador.incrementAndGet();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
contador.incrementAndGet();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {}
System.out.println("Contador final: " + contador.get());
}
}
Conclusão
A palavra-chave volatile
melhora a visibilidade das variáveis entre threads, mas não impede condições de corrida. Seu uso é indicado para variáveis imutáveis ou flags de controle, mas para operações atômicas, recomenda-se synchronized
, Lock
ou AtomicInteger
.
Por que volatile nem sempre resolve problemas de concorrência?
A sincronização de variáveis em aplicações multithread é um dos desafios mais complexos da programação concorrente. O uso incorreto de cache de CPU pode levar a comportamentos inesperados, onde uma thread não enxerga as mudanças feitas por outra. A palavra-chave volatile
é uma solução simples, mas limitada, pois apenas garante a visibilidade das alterações e não evita condições de corrida. Para cenários onde múltiplas operações precisam ser sincronizadas, o uso de synchronized
, Locks
ou classes atômicas como AtomicInteger
é mais adequado.
Algumas aplicações:
- Garantia de visibilidade de mudanças de estado em aplicações concorrentes
- Implementação de variáveis de controle (ex: flags de interrupção)
- Evitar problemas de cache em sistemas multithread
- Melhoria da comunicação entre threads sem a necessidade de sincronização completa
Dicas para quem está começando
- Use
volatile
apenas para variáveis de controle de estado - Evite usar
volatile
em variáveis que requerem operações atômicas - Se precisar de sincronização completa, utilize
synchronized
ouLocks
- Para contadores e incrementos seguros, prefira
AtomicInteger
Contribuições de Rodrigo Farias