Atualizado: 07/09/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Sincronização de Threads em Java: O Bloco e Método synchronized

Quando múltiplas threads acessam e modificam simultaneamente um recurso compartilhado, como um objeto em memória, o resultado pode se tornar incorreto e imprevisível. Esse cenário, onde o resultado da operação depende da ordem de execução imprevisível das threads, é conhecido como condição de corrida (race condition).

Considere o seguinte exemplo:

class CommonResource {
    int x = 0;
}

class CountThread implements Runnable {
    CommonResource res;

    CountThread(CommonResource res) {
        this.res = res;
    }

    public void run() {
        res.x = 1;
        for (int i = 1; i < 5; i++) {
            System.out.printf("%s %d \n", Thread.currentThread().getName(), res.x);
            res.x++;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {}
        }
    }
}

public class Program {
    public static void main(String[] args) {
        CommonResource commonResource = new CommonResource();
        for (int i = 1; i < 6; i++) {
            Thread t = new Thread(new CountThread(commonResource));
            t.setName("Thread " + i);
            t.start();
        }
    }
}

Neste código, a intenção é que cada uma das cinco threads reinicie o contador res.x para 1 e o incremente quatro vezes. Contudo, o resultado da execução é caótico:

Thread 1 1
Thread 2 1
Thread 3 1
Thread 5 1
Thread 4 1
Thread 5 6
Thread 2 6
...

O problema ocorre porque a operação res.x++ não é atômica (ou seja, não é executada como uma única instrução indivisível). Ela, na verdade, consiste em três passos distintos:

  1. ler o valor atual de x,
  2. somar 1 ao valor lido, e
  3. escrever o novo valor de volta em x.

Uma thread pode ser pausada pelo sistema operacional entre qualquer um desses passos. Por exemplo, a Thread A lê x (valor 5), mas antes de escrever o novo valor 6, a Thread B também lê x (que ainda é 5). Ambas as threads acabarão escrevendo 6 de volta, resultando em um incremento perdido, já que o valor final deveria ser 7.

O Bloco synchronized

Para garantir a execução atômica de um trecho de código, Java oferece o bloco synchronized. Ele cria uma seção crítica, um bloco de código que apenas uma thread por vez pode executar.

Vamos modificar a classe CountThread:

class CountThread implements Runnable {
    CommonResource res;

    CountThread(CommonResource res) {
        this.res = res;
    }

    public void run() {
        synchronized (res) {
            res.x = 1;
            for (int i = 1; i < 5; i++) {
                System.out.printf("%s %d \n", Thread.currentThread().getName(), res.x);
                res.x++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {}
            }
        }
    }
}

A palavra-chave synchronized é seguida por um objeto entre parênteses (res, neste caso), que atua como um objeto de bloqueio (lock).

Tecnicamente, cada objeto em Java possui um monitor intrínseco. Para executar o código dentro de um bloco synchronized, uma thread deve primeiro adquirir o lock do monitor associado ao objeto especificado.

Apenas uma thread pode possuir o lock de um determinado monitor por vez. Se uma thread adquire o lock de res, qualquer outra thread que tente entrar em um bloco sincronizado pelo mesmo objeto res será bloqueada e colocada em um estado de espera. O lock é liberado automaticamente quando a thread que o detém conclui a execução do bloco, permitindo que uma das threads em espera o adquira e prossiga.

Com essa alteração, a saída se torna ordenada e previsível:

Thread 1 1
Thread 1 2
Thread 1 3
Thread 1 4
Thread 3 1
Thread 3 2
Thread 3 3
Thread 3 4
...

O Método synchronized

Uma alternativa conveniente é declarar um método inteiro como sincronizado. Isso é útil quando toda a lógica do método precisa de exclusão mútua.

Vamos refatorar o código, movendo a lógica para um método synchronized na classe do recurso compartilhado:

class CommonResource {
    int x = 0;

    synchronized void increment() {
        x = 1;
        for (int i = 1; i < 5; i++) {
            System.out.printf("%s %d \n", Thread.currentThread().getName(), x);
            x++;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {}
        }
    }
}

class CountThread implements Runnable {
    CommonResource res;

    CountThread(CommonResource res) {
        this.res = res;
    }

    public void run() {
        res.increment();
    }
}
// A classe Program e o método main permanecem os mesmos

Declarar um método de instância como synchronized é um atalho para envolver todo o seu corpo em um bloco synchronized(this).

Isso significa que o bloqueio é adquirido no monitor da própria instância do objeto (commonResource, neste caso) em que o método foi chamado. Como todas as threads compartilham a mesma instância de commonResource, elas competem pelo mesmo lock antes de executar o método increment(), garantindo o mesmo comportamento de exclusão mútua visto anteriormente.

Resumo

  • Condições de corrida (race conditions) ocorrem quando múltiplas threads manipulam dados compartilhados simultaneamente, levando a resultados inconsistentes.
  • A palavra-chave synchronized é usada para criar seções críticas, garantindo que apenas uma thread por vez execute um trecho de código ou método.
  • A sincronização utiliza um monitor de objeto como um bloqueio (lock) para garantir a exclusão mútua.
  • Métodos de instância synchronized usam o monitor da própria instância (this) como o objeto de bloqueio.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com