CodeGym /Cursos /JAVA 25 SELF /Mútexes e semáforos: sintaxe e tarefas

Mútexes e semáforos: sintaxe e tarefas

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

1. Mútex (Mutex): o que é e como funciona

Mútex (do inglês “mutual exclusion” — “exclusão mútua”) — é um mecanismo que permite que apenas uma thread execute simultaneamente uma seção crítica do código. Se o mútex estiver ocupado (capturado por outra thread), as demais threads esperam até que ele seja liberado.

Em Java, o papel de um mútex é frequentemente desempenhado pelo objeto no qual o código é sincronizado: synchronized. A partir da versão 5 do Java, surgiu a classe ReentrantLock — uma implementação de mútex mais explícita e flexível.

Esquematicamente

Imagine uma sala com uma única chave (mútex). Para entrar, é preciso pegar a chave. Se a chave não estiver disponível (alguém já a pegou), você espera na porta. Assim que a chave retorna ao lugar (o mútex é liberado), a próxima pessoa pode entrar.

Sintaxe de mútex em Java

Com synchronized (clássico):

public class Counter {
    private int count = 0;

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

Aqui todo o método increment está protegido por um mútex — apenas uma thread pode executá-lo em um dado momento.

Com ReentrantLock (mais flexível):

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(); // Adquirimos o mútex
        try {
            count++;
        } finally {
            lock.unlock(); // Libere sempre!
        }
    }
}

Importante! Sempre libere o mútex no bloco finally, caso contrário você pode ter um “bloqueio eterno” (deadlock) — e o programa vai travar.

Quando é necessário um mútex?

Um mútex é necessário quando apenas uma thread por vez deve acessar um recurso. Pode ser uma variável, arquivo ou banco de dados. É especialmente importante usar um mútex quando o trabalho com o recurso não é atômico: até mesmo count++ na verdade consiste em três etapas — ler o valor, incrementar e gravar de volta. Sem um mútex, várias threads podem interferir entre as etapas e provocar uma condição de corrida.

2. Semáforo (Semaphore): para que serve e como funciona

Semáforo — é um “regulador” que permite que várias threads trabalhem simultaneamente com um recurso, mas não mais do que uma quantidade definida. Se o limite estiver esgotado, as demais threads esperam a sua vez.

Analogia: um estacionamento para 3 carros. Se todas as vagas estiverem ocupadas, quem chega depois espera até que alguém saia.

Sintaxe de semáforo em Java

Para isso usa-se a classe Semaphore do pacote java.util.concurrent:

import java.util.concurrent.Semaphore;

public class ParkingLot {
    private final Semaphore spots;

    public ParkingLot(int places) {
        this.spots = new Semaphore(places);
    }

    public void parkCar(String car) throws InterruptedException {
        spots.acquire(); // Tentamos ocupar uma vaga (se não houver — aguardamos)
        try {
            System.out.println(car + " estacionou.");
            Thread.sleep(1000); // O carro permanece estacionado
        } finally {
            spots.release(); // Liberamos a vaga
            System.out.println(car + " foi embora.");
        }
    }
}

Uso:

ParkingLot parking = new ParkingLot(3);

for (int i = 1; i <= 5; i++) {
    final String car = "Carro " + i;
    new Thread(() -> {
        try {
            parking.parkCar(car);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

Resultado: simultaneamente não haverá mais de três carros no estacionamento — os demais aguardam.

Como o semáforo funciona?

  • Ao criar um semáforo, define-se a quantidade de “permissões” (permits).
  • O método acquire() tenta obter uma permissão: se houver disponível — a thread avança, se não — espera.
  • O método release() devolve a permissão.
  • Um semáforo com uma única permissão se comporta quase como um mútex, mas sem “proprietário”.

3. Mútex e semáforo: qual é a diferença?

Característica Mútex (Mutex) Semáforo (Semaphore)
Quantidade de threads Apenas uma Várias (número limitado)
Aplicação Proteção de recurso Restrição de acesso (por exemplo, pool)
API em Java
synchronized, Lock
Semaphore
Controle Normalmente, há um “proprietário” Pode ser liberado por qualquer thread
Cenário típico Contador compartilhado, objeto Pool de conexões, estacionamento, limite

- Mútex — para os casos em que é necessário acesso exclusivo.
- Semáforo — quando é possível permitir vários, mas não todos.

Analogia: mútex — banheiro com uma cabine; semáforo — banheiro com três cabines.

4. Exemplos práticos

Exemplo 1: Mútex para proteger a seção crítica

Suponha que temos um banco compartilhado e várias threads transferem dinheiro entre contas. As operações devem ser atômicas.

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

public class BankAccount {
    private int balance;
    private final Lock lock = new ReentrantLock();

    public BankAccount(int initial) {
        this.balance = initial;
    }

    public void deposit(int amount) {
        lock.lock();
        try {
            balance += amount;
        } finally {
            lock.unlock();
        }
    }

    public void withdraw(int amount) {
        lock.lock();
        try {
            if (balance >= amount) {
                balance -= amount;
            }
        } finally {
            lock.unlock();
        }
    }

    public int getBalance() {
        return balance;
    }
}

Aqui, quaisquer operações com o saldo estão protegidas por um mútex, para que não ocorra condição de corrida.

Exemplo 2: Semáforo para limitar o acesso

Um servidor pode processar simultaneamente apenas 2 clientes (por exemplo, por causa de licença).

import java.util.concurrent.Semaphore;

public class Server {
    private final Semaphore connections = new Semaphore(2);

    public void handleRequest(String client) throws InterruptedException {
        connections.acquire();
        try {
            System.out.println(client + " conectou-se ao servidor.");
            Thread.sleep(2000); // Simulação de processamento da requisição
        } finally {
            connections.release();
            System.out.println(client + " desconectou-se.");
        }
    }
}

Uso:

Server server = new Server();

for (int i = 1; i <= 5; i++) {
    final String client = "Cliente " + i;
    new Thread(() -> {
        try {
            server.handleRequest(client);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

Resultado: o servidor atende simultaneamente no máximo dois clientes.

5. Particularidades e nuances de uso

Mútex: libere sempre!
É muito importante não esquecer de chamar unlock() (ou sair do bloco sincronizado) mesmo em caso de exceções. Use try-finally:

lock.lock();
try {
    // seção crítica
} finally {
    lock.unlock();
}

Se esquecer — você pode obter um “bloqueio eterno”; outras threads vão esperar indefinidamente.

Semáforo: é possível liberar de outra thread?
Ao contrário do mútex, release() pode ser chamado por qualquer thread, inclusive aquela que não fez acquire(). Isso às vezes é útil, mas é fácil errar — mantenha disciplina.

Semaphore com uma permissão = mútex?
Quase. Mas o semáforo não tem o conceito de “proprietário”: qualquer liberação incrementa o contador de permissões. Já no mútex, quem libera deve ser quem o adquiriu.

Não confunda semáforo e pool
Semáforo não é um pool de objetos, é apenas um “contador de permissões”. Ele é frequentemente usado para implementar pools (por exemplo, um pool de conexões com o BD), mas por si só não armazena nada.

6. Erros comuns ao trabalhar com mútexes e semáforos

Erro nº 1: Esquecer de chamar unlock/release. Se você adquiriu um mútex ou um semáforo mas não chamou unlock() ou release(), outras threads podem ficar bloqueadas para sempre. Sempre use try-finally para garantir a liberação do lock mesmo em caso de exceções.

Erro nº 2: Sincronizar no objeto errado. Se sincronizar em uma variável que não é compartilhada por todas as threads (por exemplo, uma variável local ou um literal de string), a sincronização não funcionará.

Erro nº 3: Liberação dupla. No caso do semáforo: se chamar release() mais vezes do que foram feitos acquire(), a quantidade de permissões aumentará além do limite. Mantenha o balanceamento!

Erro nº 4: Usar semáforo em vez de mútex (ou vice-versa). Se for necessário acesso exclusivo, use um mútex (synchronized ou Lock). Se for preciso limitar o número de threads trabalhando simultaneamente — use Semaphore.

Erro nº 5: Manter o bloqueio por muito tempo. Quanto mais tempo uma thread mantém um mútex ou semáforo, mais tempo as outras esperam. Minimize o tempo de trabalho dentro da seção crítica.

1
Tarefa
JAVA 25 SELF, nível 52, lição 3
Bloqueado
Monitoramento de eventos críticos 🚨
Monitoramento de eventos críticos 🚨
1
Tarefa
JAVA 25 SELF, nível 52, lição 3
Bloqueado
Gerenciamento de acesso às pistas de pouso e decolagem ✈️
Gerenciamento de acesso às pistas de pouso e decolagem ✈️
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION