Programação Concorrente com Semáforos em Java: Guia Prático para Iniciantes

Quando estamos desenvolvendo aplicações que executam múltiplas threads simultaneamente, é crucial gerenciar o acesso a recursos compartilhados de forma eficiente para evitar conflitos e garantir a estabilidade do sistema. É aqui que entra o conceito de semáforos em programação. Em Java, o Semaphore
é uma implementação de semáforo que pode ser extremamente útil para controlar o acesso a recursos limitados.
O que é um Semaphore?
O Semaphore
é uma classe da biblioteca java.util.concurrent
que foi introduzida no Java 5 como parte das utilidades de concorrência. Um semáforo mantém um conjunto de permissões, e estas permissões são concedidas às threads que querem acessar o recurso. Se uma thread solicita uma permissão e o semáforo possui permissões disponíveis, ele concede a permissão à thread. Caso contrário, a thread fica bloqueada até que uma permissão seja liberada.
Como usar um Semaphore
Vamos começar com um exemplo básico para entender como usar um Semaphore
. Suponha que você tenha um recurso que só pode ser acessado por um número limitado de threads simultaneamente. Aqui está como você pode usar um Semaphore
para gerenciar isso:
import java.util.concurrent.Semaphore;
public class FilaDeImpressao {
private final Semaphore semaforo;
public FilaDeImpressao(int maxConcorrentes) {
semaforo = new Semaphore(maxConcorrentes);
}
public void imprimirDocumento(Object documento) {
try {
semaforo.acquire();
System.out.println("Imprimindo documento: " + documento);
// Simulando o tempo para imprimir um documento
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaforo.release();
}
}
}
No exemplo acima, a classe FilaDeImpressao
simula uma fila de impressão onde múltiplos trabalhos podem ser impressos, mas apenas um número limitado de impressões pode ocorrer simultaneamente. O Semaphore
é inicializado com o número máximo de "trabalhos" que podem ser executados em paralelo.
O método acquire()
é usado para solicitar uma permissão ao Semaphore
. Se uma permissão estiver disponível, ele é concedido à thread e a execução continua. Caso contrário, a thread será bloqueada até que uma permissão esteja disponível. O método release()
é utilizado para liberar a permissão de volta ao semáforo, indicando que a thread terminou de usar o recurso.
Exemplo de controle de acesso
Vejamos outro exemplo, onde o Semaphore
é utilizado para controlar o acesso a uma seção crítica que não deve ser acessada por mais de N threads ao mesmo tempo:
import java.util.concurrent.Semaphore;
public class ControleDeAcesso {
private final Semaphore semaforo = new Semaphore(5); // Permite até 5 acessos simultâneos
public void acessarRecurso() {
try {
semaforo.acquire();
System.out.println("Acessando a seção crítica");
// Simulando trabalho dentro da seção crítica
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaforo.release();
}
}
}
Conclusão
O uso de Semaphore
em Java é uma forma eficaz de controlar o acesso a recursos que têm uma capacidade limitada. Seja em aplicações que necessitam de controle fino sobre o acesso a seções críticas, ou em situações onde você precisa gerenciar um conjunto limitado de recursos, o Semaphore
oferece uma solução robusta e relativamente simples de implementar. Para desenvolvedores iniciantes em Java, entender e saber implementar um Semaphore
corretamente pode significativamente melhorar a robustez e a eficiência de suas aplicações multi-threaded.