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 | |
|
| 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.
GO TO FULL VERSION