Synchronized é um mecanismo de sincronização integrado na linguagem Java que fornece acesso exclusivo a secções críticas de código. De acordo com Oracle, 2024, o modificador synchronized garante que apenas uma thread pode executar o método ou bloco marcado num momento específico. Este mecanismo baseia-se em monitores — um conceito fundamental dos sistemas operativos que assegura o correto funcionamento de aplicações multithread de todos os níveis de complexidade.
Pontos principais
Synchronized é uma palavra-chave em Java que garante que apenas uma thread executa uma secção protegida de código de cada vez, prevenindo a corrupção de dados durante o acesso concorrente. Apareceu na primeira versão do Java e continua a ser a forma mais simples de garantir thread safety para programadores de qualquer nível.
O modificador synchronized resolve duas tarefas: exclusão mútua e visibilidade de alterações. Quando uma thread sai de um bloco synchronized, todas as alterações são garantidamente visíveis para outras threads que entrem num bloco sincronizado no mesmo objeto.
Synchronized pode ser aplicado a um método inteiro ou a um bloco de código arbitrário especificando um objeto monitor. Em ambos os casos, a JVM insere instruções monitorenter e monitorexit ao nível do bytecode.
Em aplicações multithread sem sincronização, ocorre uma condição de corrida (race condition) quando duas threads modificam simultaneamente os mesmos dados, levando a resultados imprevisíveis. Synchronized tornou-se a primeira e principal ferramenta do Java para combater este problema, fornecendo uma sintaxe declarativa simples acessível a qualquer programador.
O mecanismo synchronized baseia-se no conceito de monitor — um primitivo de sincronização de alto nível incorporado em cada objeto Java. O monitor é associado a um objeto quando um bloco synchronized é usado pela primeira vez sobre ele.
Cada objeto em Java tem um monitor associado. Quando uma thread entra num bloco synchronized, adquire o monitor do objeto. Se o monitor já estiver ocupado por outra thread, a thread bloqueia até ser libertado. Em bytecode, isto corresponde ao par de instruções monitorenter e monitorexit.
A JVM otimiza o synchronized através de vários níveis: biased locking (bloqueio tendencioso) para acesso de thread única, lightweight locking (bloqueio leve) para baixa contenção, e heavyweight locking (bloqueio pesado) para contenção intensa com envolvimento do SO. Estes níveis melhoram o desempenho sem alterar o código.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Synchronized estabelece uma relação happens-before: todas as ações numa thread antes de sair de um bloco synchronized são visíveis para outra thread depois de entrar num bloco sincronizado no mesmo objeto. Isto garante não apenas exclusão mútua mas também consistência de dados para todas as threads.
Java oferece duas formas de aplicar synchronized: ao nível do método e ao nível do bloco. A escolha entre eles afeta o desempenho e a granularidade da sincronização.
Marcar um método com o modificador synchronized sincroniza-o automaticamente na instância atual (para métodos de instância) ou no objeto Class (para métodos estáticos). É a forma mais simples de garantir exclusão mútua, mas é muitas vezes excessiva se a secção crítica constituir apenas uma pequena parte do método e o resto do código não precisar de sincronização.
Um bloco synchronized dá controlo preciso: especifica o objeto monitor e sincroniza apenas a secção necessária do código, deixando o resto do método fora do bloqueio. Isto minimiza o tempo de retenção do monitor e melhora o desempenho geral da aplicação num ambiente multithread, pois outras threads podem executar código não relacionado em paralelo sem esperar pela libertação do monitor.
class DataProcessor {
private final Object lock = new Object();
public void process() {
// código fora da secção crítica - sem sincronização
prepareData()
synchronized (lock) {
// apenas este bloco está protegido
updateSharedState()
}
// continuação sem bloqueio
cleanup()
}
}
| Critério | Método synchronized | Bloco synchronized |
|---|---|---|
| Monitor | this (instância) ou Class | qualquer objeto |
| Granularidade | método inteiro | apenas o código necessário |
| Legibilidade | alta | média |
| Desempenho | menor para métodos grandes | maior para secções críticas pequenas |
No desenvolvimento Android, synchronized é amplamente usado para proteger SharedPreferences, acesso a bases de dados e componentes de UI. No entanto, a sua utilização na thread principal é fortemente desaconselhada devido ao risco de congelamento da interface.
SharedPreferences no Android fornece thread safety básica, mas ao editar a partir de múltiplas threads através do Editor, pode ser necessária sincronização externa. Um bloco synchronized com um objeto de bloqueio separado garante consistência das alterações.
class PreferencesManager(private val prefs: SharedPreferences) {
private val lock = Any()
fun writeToken(token: String) {
synchronized (lock) {
prefs.edit()
.putString("auth_token", token)
.apply()
}
}
}
A principal limitação do synchronized no Android é o bloqueio de thread. Ao contrário das corrotinas com Mutex, o synchronized bloqueia a thread do sistema por completo. Na thread principal, isto causa ANR. No desenvolvimento Android moderno, recomenda-se substituir synchronized por corrotinas (suspend Mutex) ou tipos atómicos (AtomicInteger).
O Java e Kotlin modernos oferecem várias alternativas ao synchronized, cada uma resolvendo os mesmos problemas com menos limitações ou melhor desempenho.
A interface Lock com as implementações ReentrantLock e ReadWriteLock fornece timeouts, espera interrompível e múltiplas filas Condition. É mais flexível que synchronized mas requer libertação explícita no finally, o que aumenta o risco de erro se o unlock for esquecido.
AtomicInteger, AtomicLong, AtomicReference e outras classes usam algoritmos Lock-Free baseados em CAS (Compare-And-Swap). São significativamente mais rápidas que synchronized em cenários de contenção moderada porque não bloqueiam threads mas realizam tentativas otimistas sem necessitar de comutação de contexto do kernel do SO.
ThreadLocal fornece uma abordagem alternativa: cada variável ThreadLocal está isolada dentro de uma única thread e não requer sincronização para leitura e escrita. Isto elimina completamente a necessidade de synchronized para dados que não devem ser partilhados entre threads. ThreadLocal é ativamente usado em frameworks (Spring, Hibernate) para armazenar contexto de transações e sessões.
Em projetos Kotlin para Android, uma alternativa ao synchronized é Mutex do kotlinx.coroutines. Não bloqueia a thread do sistema operativo mas suspende a corrotina até o bloqueio ser libertado — isto permite uma utilização eficiente das threads do pool e evita ANR durante longas esperas pela libertação do recurso.
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
var counter = 0
suspend fun safeIncrement() {
mutex.withLock {
counter++
}
}
O desempenho do synchronized mudou significativamente nas versões recentes do Java. Antes era considerado um mecanismo “pesado”, mas as JVMs modernas eliminaram a maior parte da sobrecarga através de otimizações avançadas do compilador JIT. Vejamos em detalhe como a máquina virtual acelera o código sincronizado em tempo de execução.
O compilador JIT da JVM aplica várias otimizações: biased locking elimina a sincronização se o bloqueio for sempre adquirido pela mesma thread; lock coarsening funde blocos synchronized adjacentes num só; lock elimination remove a sincronização se o objeto for apenas acessível por uma thread. Estas otimizações tornam o synchronized praticamente gratuito com baixa contenção.
A JVM determina o nível de contenção para cada objeto: quando não há contenção, o biased locking é ativado; quando uma segunda thread aparece, o bloqueio transita para modo leve com espera ativa (spin); e apenas durante espera prolongada escala para pesado com um mutex do sistema. Esta escalada ocorre automaticamente, e o programador não precisa de escolher manualmente uma estratégia.
Em benchmarks modernos (Java 17+), synchronized mostra desempenho comparável ao ReentrantLock com contenção baixa e moderada. Com alta contenção, o Lock pode ter vantagem graças a uma fila de espera mais eficiente com suporte de timeouts e interrupção. Para sistemas de alta carga onde a contenção é constante, o ReentrantLock com modo fair oferece um comportamento mais previsível.
As classes atómicas (AtomicInteger, AtomicReference) continuam a ser as mais rápidas para contadores e flags simples graças à implementação Lock-Free baseada em CAS. Não bloqueiam threads de todo — em caso de conflito, a operação simplesmente repete-se num ciclo. Isto proporciona um ganho de desempenho de 3 a 5 vezes em comparação com synchronized em operações de incremento de contador com 4-8 threads.
Perguntas frequentes
Synchronized fornece tanto exclusão mútua como visibilidade. Volatile apenas garante visibilidade de alterações — escrever numa variável volatile é visível para todas as threads mas não impede a modificação simultânea, ou seja, não protege contra condições de corrida.
Sim, é possível um deadlock com sincronização aninhada usando diferentes ordens de monitores. Por exemplo, uma thread chama synchronized(a) { synchronized(b) }, enquanto outra chama synchronized(b) { synchronized(a) }. Evite blocos synchronized aninhados ou fixe uma ordem de monitores consistente.
Um monitor é um mecanismo de sincronização associado a cada objeto Java. Garante que apenas uma thread executa código synchronized nesse objeto. O monitor inclui um bloqueio, uma fila de espera e um conjunto de threads à espera de notificação através de wait/notify.
Nas versões modernas do Java (17+), synchronized não fica atrás do Lock em desempenho graças às otimizações JIT (biased locking, lock coarsening). O Lock é preferido não pela velocidade mas pelas capacidades adicionais: timeouts, espera interrompível e múltiplas filas Condition.
Um método estático synchronized usa o monitor do objeto Class da classe dada, não da instância. Isto significa que a sincronização se aplica a todas as instâncias da classe. Métodos synchronized não estáticos e estáticos usam monitores diferentes e não se bloqueiam mutuamente.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também