CodeGym /Cursos /JAVA 25 SELF /Análise de erros comuns de sincronização

Análise de erros comuns de sincronização

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

1. Unlock/release esquecido: armadilha para desatentos

Um dos erros mais traiçoeiros ao usar ferramentas modernas de sincronização, como ReentrantLock ou Semaphore, é esquecer de chamar unlock() ou release(). Se você não liberar o bloqueio, outras threads vão esperar pela liberação dele... para sempre. O programa vai travar e você vai ficar olhando para a tela, tentando entender por que nada acontece.

Vamos ver um exemplo com ReentrantLock:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        // Opa! Esquecemos de chamar unlock() — agora todos vão travar!
        count++;
    }
}

Parece inofensivo, mas se você chamar increment() várias vezes de threads diferentes, após a primeira chamada as demais threads vão esperar a liberação do bloqueio indefinidamente.

Para evitar isso, use o padrão try-finally:

public void increment() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();
    }
}

Agora, mesmo que uma exceção ocorra no meio do método, o bloqueio será liberado de forma garantida.

É como se alguém ocupasse o banheiro (trancando por dentro) e depois esquecesse de destrancar a porta e saísse pela janela. Os outros vão ficar esperando até essa pessoa “sair”... Não faça isso!

2. Sincronizar no objeto errado: “Ops, pendurei o cadeado no lugar errado!”

Em Java, a palavra-chave synchronized pode bloquear o acesso a um objeto. Mas se você escolher o objeto errado para bloquear, a sincronização não vai funcionar como você espera.

Erro nº 1: sincronizar em uma variável local

public void doSomething() {
    Object lock = new Object();
    synchronized (lock) {
        // Toda vez é um objeto novo — não há sincronização!
        // As threads não esperam umas pelas outras.
        // A seção crítica não está protegida!
    }
}

Aqui, cada thread cria seu próprio objeto lock. Como resultado, nenhum bloqueio real acontece — as threads entram na seção crítica ao mesmo tempo.

Correto:

private final Object lock = new Object();

public void doSomething() {
    synchronized (lock) {
        // Agora todas as threads usam o mesmo objeto lock
        // e de fato esperam umas pelas outras.
    }
}

Erro nº 2: sincronizar em um literal de string

public void doSomething() {
    synchronized ("lock") {
        // Literais de string são internados: partes diferentes do programa podem
        // acidentalmente sincronizar no mesmo literal!
    }
}

Conclusão:
Sincronize apenas em objetos privados, criados especificamente para isso, que não sejam usados em nenhum outro lugar.

3. Bloqueio mútuo (deadlock): “Você espera por mim, eu por você — e ninguém anda”

Deadlock (bloqueio mútuo) é um clássico. Duas (ou mais) threads adquirem, alternadamente, bloqueios diferentes e ficam esperando uma pela outra, até o programa travar completamente.

Exemplo:

public class DeadlockExample {
    private final Object lockA = new Object();
    private final Object lockB = new Object();

    public void method1() {
        synchronized (lockA) {
            // Vamos esperar um pouco para fins de demonstração
            try { Thread.sleep(50); } catch (InterruptedException e) {}
            synchronized (lockB) {
                // ...
            }
        }
    }

    public void method2() {
        synchronized (lockB) {
            try { Thread.sleep(50); } catch (InterruptedException e) {}
            synchronized (lockA) {
                // ...
            }
        }
    }
}

Se uma thread chamar method1() e outra — method2(), a primeira thread vai adquirir lockA e esperar por lockB, enquanto a segunda fará o oposto. Como resultado, ambas vão esperar indefinidamente.

Como evitar?

  • Sempre adquira os locks na mesma ordem em todas as threads.
  • Minimize a quantidade de locks mantidos simultaneamente.
  • Use ferramentas de diagnóstico (por exemplo, jstack) se o programa travar.

Analogia:
É como se duas pessoas se encontrassem em um corredor estreito, e cada uma decidisse ceder a passagem, mas somente se a outra cedesse primeiro. No fim, ambas ficam paradas esperando até que alguém desista.

4. Sincronização excessiva: “Melhor pecar pelo excesso?” — nem sempre!

Às vezes, com medo de erros, desenvolvedores sincronizam tudo. Como resultado, a performance cai e o benefício é zero.

Exemplo:

public synchronized void add(int value) {
    // Aqui há apenas uma linha que não requer sincronização!
    System.out.println("Adicionado: " + value);
}

Neste caso, a sincronização não é necessária: a saída na tela via System.out.println já é thread-safe, e o próprio método não trabalha com recursos compartilhados.

Onde isso é crítico?
Se você sincroniza métodos que são chamados com frequência e não precisam de proteção, reduz drasticamente o desempenho do programa. As threads formam uma fila, embora pudessem trabalhar em paralelo.

Best practice:
Sincronize apenas o que realmente for necessário. A seção crítica deve ser a menor possível.

5. Uso incorreto de volatile: “Há visibilidade, mas não há atomicidade!”

O modificador volatile em Java garante que as mudanças na variável serão visíveis para todas as threads. Mas ele não garante a atomicidade das operações.

Erro:

private volatile int counter = 0;

public void increment() {
    counter++; // Não é atômico!
}

A operação counter++ consiste em ler o valor, incrementá-lo e gravá-lo de volta. Se duas threads executarem esse código ao mesmo tempo, o valor final pode ser menor do que o esperado.

Correto:
Para operações atômicas, use synchronized, AtomicInteger ou outras classes thread-safe.

import java.util.concurrent.atomic.AtomicInteger;

private final AtomicInteger counter = new AtomicInteger();

public void increment() {
    counter.incrementAndGet();
}

Quando usar volatile?
Para flags simples (por exemplo, “encerrar execução”), quando não é necessária atomicidade.

1
Pesquisa/teste
Sincronização de threads, nível 52, lição 4
Indisponível
Sincronização de threads
Sincronização de threads
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION