CodeGym /Cursos /JAVA 25 SELF /synchronized, volatile: sintaxe e uso

synchronized, volatile: sintaxe e uso

JAVA 25 SELF
Nível 52 , Lição 1
Disponível

1. Palavra‑chave synchronized: por que e como

Em Java, a palavra‑chave synchronized — é como uma placa “Ocupado!” na porta do banheiro: enquanto uma thread está dentro da “seção crítica”, as demais esperam educadamente a sua vez. Só quando a primeira sair, a próxima poderá entrar e executar seu código.

Sintaxe: bloco e método

Bloco sincronizado

synchronized (object) {
    // seção crítica
}
  • object — é qualquer objeto no qual você quer “pendurar o cadeado”. Enquanto uma thread executa esse bloco, outras threads que também querem entrar em um bloco com esse mesmo objeto vão esperar.

Método sincronizado

public synchronized void increment() {
    // seção crítica
}
  • Aqui, o “cadeado” é colocado no próprio objeto (this). Ou seja, apenas uma thread por vez pode executar qualquer método sincronizado desse objeto.

Método estático sincronizado

public static synchronized void foo() {
    // seção crítica
}
  • Aqui o bloqueio ocorre no nível da classe (ClassName.class), e não de um objeto específico.

Como isso funciona por baixo dos panos

Quando uma thread entra em um bloco ou método sincronizado, ela captura o “monitor” do objeto (ou da classe, para métodos estáticos). Se o monitor já estiver ocupado — a thread espera. Assim que o monitor é liberado, a próxima thread pode entrar.

2. Exemplo: incremento do contador com e sem sincronização

Sem sincronização

public class Counter {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}
public class CounterDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("Valor final: " + counter.getCount());
    }
}

Valor esperado: 2000
Valor real: pode ser menor (por exemplo, 1995, 1987...), e a cada execução — uma “surpresa” diferente.

Por quê? Porque a operação count++ não é atômica: ela se divide em três passos — ler o valor, incrementar e gravar de volta. Se duas threads fizerem isso simultaneamente, elas podem sobrescrever o resultado uma da outra.

Solução: synchronized

public class Counter {
    private int count = 0;

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

    public int getCount() {
        return count;
    }
}

Agora apenas uma thread por vez pode executar o método increment(). O valor final sempre será 2000.

Alternativa: bloco sincronizado

public class Counter {
    private int count = 0;

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

O resultado será o mesmo. Você pode sincronizar não o método inteiro, mas apenas a parte necessária.

3. Introdução ao “monitor do objeto”

O monitor é um “cadeado” embutido em cada objeto em Java. Quando você escreve synchronized(object), a thread tenta “trancar” esse objeto. Se o cadeado estiver livre — a thread o obtém; caso contrário — ela espera sua vez. Assim que a thread sai do bloco, o cadeado é liberado.

Importante! Se você sincroniza em objetos diferentes — as threads não vão esperar umas pelas outras. Portanto, é muito importante escolher o objeto correto para sincronização.

Métodos estáticos sincronizados

Às vezes, o recurso compartilhado não é um objeto, mas algo comum a todas as instâncias da classe (por exemplo, uma variável estática). Nesse caso, a sincronização deve ser no nível da classe.

public class StaticCounter {
    private static int count = 0;

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

    public static int getCount() {
        return count;
    }
}

Isto é equivalente a:

public static void increment() {
    synchronized (StaticCounter.class) {
        count++;
    }
}

O monitor fica no objeto da classe (Class), e não em uma instância específica.

4. Palavra‑chave volatile: o que é e para que serve

Problema de visibilidade entre threads

Em Java, cada thread pode armazenar em cache os valores das variáveis para acelerar a execução. Isso significa que, se uma thread alterou uma variável, outra thread pode “não perceber”, continuando a ler o valor do seu cache local. Isso é especialmente crítico para flags com as quais as threads sinalizam umas às outras.

Como funciona o volatile

Se uma variável for declarada como volatile, isso significa:

  • Todas as threads sempre leem e escrevem nela apenas na memória principal, ignorando o cache.
  • Qualquer alteração na variável se torna imediatamente visível para todas as threads.

Mas! As operações com volatile por si só não são atômicas (exceto leitura/gravação simples de primitivos como boolean, int etc.). Se você faz algo mais complexo do que uma atribuição — é necessária sincronização.

Exemplo: flag de encerramento

public class Worker extends Thread {
    private volatile boolean running = true;

    public void run() {
        while (running) {
            // fazemos algo útil
        }
        System.out.println("Thread finalizada");
    }

    public void shutdown() {
        running = false;
    }
}
Worker w = new Worker();
w.start();
// ... depois de algum tempo
w.shutdown();

Sem volatile, a thread pode “não perceber” a alteração do flag e entrar em loop para sempre (especialmente em sistemas multi-core). Com volatile — tudo funciona como esperado.

5. Limitações do volatile: não-atomicidade

Muitos iniciantes pensam: “Se eu tornar int volatile, posso escrever count++ e não me preocupar”. Infelizmente, não é bem assim:

private volatile int count = 0;

public void increment() {
    count++;
}

Erro! A operação count++ ainda não é atômica — são três etapas: (1) ler, (2) incrementar, (3) gravar de volta. Se duas threads lerem o mesmo valor ao mesmo tempo, ambas o incrementarão e ambas gravarão o mesmo resultado — um incremento “se perde”.

Conclusão: volatile garante apenas a visibilidade das alterações, mas não protege contra condições de corrida em operações complexas.

6. Quando usar synchronized e quando — volatile

  • volatile — quando você tem um flag simples (por exemplo, boolean), que uma thread escreve e outra lê. Exemplo: encerramento de uma thread, sinalização de um evento.
  • synchronized — quando é preciso garantir a atomicidade de operações complexas (por exemplo, incremento, alteração de várias variáveis, trabalho com estruturas de dados).

Tabela para memorização

Cenário volatile synchronized
Enviar sinal entre threads
Operação atômica (incremento)
Vários passos na seção crítica
Apenas visibilidade das alterações

7. Erros comuns ao usar synchronized e volatile

Erro nº 1: Sincronizar no objeto errado. Se você sincroniza em uma variável local ou em objetos diferentes em cada thread — não haverá proteção alguma.

Object lock = new Object();
synchronized (lock) {
    // ...
}

Se cada thread criar seu próprio lock — não adianta. É necessário um ponto único de sincronização, um objeto comum para todas as threads.

Erro nº 2: Esperar atomicidade de volatile. volatile garante visibilidade, não atomicidade. Operações como count++ continuam inseguras sem sincronização.

Erro nº 3: Sincronizar uma área muito grande do código. Se você sincroniza o método inteiro, quando é necessário — apenas uma linha, você bloqueia outras threads à toa e perde desempenho. Procure reduzir a “seção crítica”.

Erro nº 4: Esquecer de tornar a sincronização “estática” para dados estáticos. Se você tem uma variável estática, mas sincroniza em this, isso não ajuda. Para dados estáticos, é necessária sincronização no nível da classe: synchronized(ClassName.class).

Erro nº 5: Sincronizar em literal de string. Sincronizar em strings é perigoso, porque literais iguais são internados pela JVM. Você pode, sem querer, obter um bloqueio compartilhado para partes diferentes do programa.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION