«Synchronized» em Java: o que é e como funciona com exemplos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

«O que significa synchronized?» — a pergunta que atormenta um em cada dois juniores

\n\n

Se você já escreveu uma aplicação multithread em Java, certamente já se deparou com esta situação: duas threads alteram simultaneamente a mesma variável e, como resultado, bugs que ocorrem uma vez a cada cem execuções. Familiar? É exatamente para isso que existe a palavra-chave synchronized. Mas o que ela realmente faz? Como funciona "por baixo dos panos"? E, o mais importante, quando usá-la e quando é melhor evitá-la?

\n\n

Neste artigo, não vamos apenas recontar a documentação. Vamos analisar uma pergunta popular do Stack Overflow (aquela que reuniu milhares de visualizações), adaptá-la para um formato educacional e mostrar uma comparação de abordagens: sincronização no nível do método, no nível do bloco e alternativas do java.util.concurrent. Vamos lá!

\n\n

O problema: Condição de Corrida (Race Condition)

\n\n

Imagine: você tem um contador que é incrementado por duas threads. O código é simples, mas o resultado é inesperado.

\n\n
public class Counter {\n    private int count = 0;\n\n    public void increment() {\n        count++;\n    }\n\n    public int getCount() {\n        return count;\n    }\n}
\n\n

Iniciamos duas threads, cada uma faz 1000 incrementos. Aparentemente, deveria ser 2000. Mas na prática — 1987, 1993, ou até 1765. Por quê? Porque a operação count++ não é atômica. Ela consiste em três etapas: leitura → incremento → escrita. As threads se "sobrepõem" e uma alteração é perdida.

\n\n

Isso é uma race condition — condição de corrida. E o synchronized é uma das principais ferramentas para combatê-la.

\n\n

O que o synchronized faz? Uma olhada por baixo do capô

\n\n

A palavra-chave synchronized em Java garante exclusão mútua (mutex) e visibilidade de alterações entre threads. Quando uma thread entra em um bloco synchronized, ela captura o monitor do objeto (ou classe). Outras threads que tentam entrar em qualquer bloco synchronized no mesmo monitor são bloqueadas até que ele seja liberado.

\n\n

Importante: o synchronized funciona apenas com um monitor. Se você tiver dois objetos diferentes, a sincronização não funcionará.

\n\n

Dois níveis de sincronização

\n
    \n
  • Método sincronizado — bloqueia o método inteiro, usa o monitor do objeto (this).
  • \n
  • Bloco sincronizado — bloqueia apenas parte do código, você pode especificar qualquer objeto como monitor.
  • \n
\n\n

Vamos compará-los na prática.

\n\n

Comparação de abordagens: método vs bloco

\n\n

1. Método sincronizado

\n\n

A maneira mais simples é adicionar synchronized à assinatura do método.

\n\n
public class SafeCounter {\n    private int count = 0;\n\n    public synchronized void increment() {\n        count++;\n    }\n\n    public synchronized int getCount() {\n        return count;\n    }\n}
\n\n

Vantagens:

\n
    \n
  • Mínimo de código, fácil de ler.
  • \n
  • Garante que todo o método seja uma operação atômica.
  • \n
\n\n

Desvantagens:

\n
    \n
  • Bloqueio "pesado" — mesmo que dentro do método haja apenas uma linha, a thread é bloqueada durante toda a execução.
  • \n
  • Reduz o desempenho se o método for longo ou chamado com frequência.
  • \n
  • Não é flexível — não é possível sincronizar apenas parte da lógica.
  • \n
\n\n

2. Bloco sincronizado

\n\n

Um controle mais refinado. Você decide qual objeto usar como monitor.

\n\n
public class SafeCounterWithBlock {\n    private int count = 0;\n    private final Object lock = new Object();\n\n    public void increment() {\n        synchronized (lock) {\n            count++;\n        }\n    }\n\n    public int getCount() {\n        synchronized (lock) {\n            return count;\n        }\n    }\n}

Blogs

Book Recommendations