Atualizado: 07/09/2025

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

Controle de Acesso com Semáforos em Java

Semáforos são uma ferramenta de sincronização que servem para controlar o acesso concorrente a um recurso. Em Java, eles são representados pela classe Semaphore, localizada no pacote java.util.concurrent.

Um semáforo funciona como um controlador de acesso baseado em um contador. Esse contador define quantas threads podem acessar um recurso ao mesmo tempo. A lógica é a seguinte:

  • Para entrar: Quando uma thread quer usar o recurso, ela chama o método acquire().

    • Se o contador for maior que zero, a thread tem a passagem liberada e o contador diminui em um.
    • Se o contador for zero, a thread fica bloqueada e precisa esperar.
  • Para sair: Quando a thread termina de usar o recurso, ela chama o método release().

    • Isso faz o contador aumentar em um, permitindo que uma das threads que estava esperando possa finalmente acessar o recurso.

O número de permissões é definido no construtor da classe Semaphore:

  • Semaphore(int permits): Cria um semáforo com o número especificado de permissões.
  • Semaphore(int permits, boolean fair): O parâmetro fair define a política de aquisição. Se true (justo), as permissões são concedidas às threads na ordem em que elas as solicitaram (FIFO). Se false (injusto, o padrão), a ordem não é garantida, o que pode levar a uma maior performance, mas também ao risco de starvation (uma thread nunca conseguir a permissão).

Para obter uma permissão, utiliza-se o método acquire(). Após o trabalho, a permissão deve ser liberada com release().

Exemplo Básico de Semáforo

Vamos usar um semáforo com uma permissão para garantir que apenas uma thread por vez possa modificar um recurso compartilhado.

import java.util.concurrent.Semaphore;

class CommonResource {
    int x = 0;
}

class CountThread implements Runnable {
    CommonResource res;
    Semaphore sem;
    String name;

    CountThread(CommonResource res, Semaphore sem, String name) {
        this.res = res;
        this.sem = sem;
        this.name = name;
    }

    public void run() {
        try {
            System.out.println(name + " aguardando permissão...");
            sem.acquire(); // Solicita uma permissão, bloqueia se não houver

            // --- Início da Seção Crítica ---
            res.x = 1;
            for (int i = 1; i < 5; i++) {
                System.out.println(this.name + ": " + res.x);
                res.x++;
                Thread.sleep(100);
            }
            // --- Fim da Seção Crítica ---

        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        } finally {
            System.out.println(name + " liberando permissão.");
            sem.release(); // Libera a permissão
        }
    }
}

public class Program {
    public static void main(String[] args) {
        Semaphore sem = new Semaphore(1); // Apenas 1 permissão
        CommonResource res = new CommonResource();
        new Thread(new CountThread(res, sem, "CountThread 1")).start();
        new Thread(new CountThread(res, sem, "CountThread 2")).start();
        new Thread(new CountThread(res, sem, "CountThread 3")).start();
    }
}

É uma prática de segurança essencial colocar a chamada sem.release() dentro de um bloco finally. Isso garante que a permissão seja sempre liberada, mesmo que uma exceção ocorra dentro da seção crítica, prevenindo que o semáforo fique permanentemente bloqueado (deadlock).

A saída do programa mostrará as threads executando a seção crítica em uma ordem não determinística, uma de cada vez:

CountThread 1 aguardando permissão...
CountThread 2 aguardando permissão...
CountThread 3 aguardando permissão...
CountThread 1: 1
CountThread 1: 2
CountThread 1: 3
CountThread 1: 4
CountThread 1 liberando permissão.
CountThread 3: 1
CountThread 3: 2
CountThread 3: 3
CountThread 3: 4
CountThread 3 liberando permissão.
CountThread 2: 1
...

O Problema dos Filósofos na Janta

Semáforos são ideais para limitar o acesso a um número finito de recursos. Um exemplo clássico é o "Problema dos Filósofos na Janta": imagine uma mesa com cinco filósofos, mas apenas duas cadeiras. No máximo dois podem se sentar à mesa simultaneamente.

import java.util.concurrent.Semaphore;

class Philosopher extends Thread {
    private Semaphore sem;
    private int mealsEaten = 0;
    private int id;

    Philosopher(Semaphore sem, int id) {
        this.sem = sem;
        this.id = id;
    }

    public void run() {
        try {
            while (mealsEaten < 3) {
                sem.acquire(); // Pega um lugar à mesa

                try {
                    System.out.println("Filósofo " + id + " senta-se à mesa.");
                    sleep(500); // Filósofo está comendo
                    mealsEaten++;
                    System.out.println("Filósofo " + id + " levanta-se da mesa.");
                } finally {
                    sem.release(); // Libera o lugar à mesa
                }

                sleep(500); // Filósofo está pensando/passeando
            }
        } catch (InterruptedException e) {
            System.out.println("Filósofo " + id + " teve problemas e se retirou.");
        }
    }
}

public class Program {
    public static void main(String[] args) {
        Semaphore table = new Semaphore(2); // Apenas 2 lugares na mesa
        for (int i = 1; i <= 5; i++) {
            new Philosopher(table, i).start();
        }
    }
}

A saída do programa mostrará que nunca haverá mais de dois filósofos "sentados à mesa" ao mesmo tempo, demonstrando o controle de capacidade do semáforo:

Filósofo 1 senta-se à mesa.
Filósofo 2 senta-se à mesa.
Filósofo 1 levanta-se da mesa.
Filósofo 3 senta-se à mesa.
Filósofo 2 levanta-se da mesa.
Filósofo 4 senta-se à mesa.
Filósofo 3 levanta-se da mesa.
Filósofo 5 senta-se à mesa.
Filósofo 4 levanta-se da mesa.
Filósofo 5 levanta-se da mesa.
Filósofo 1 senta-se à mesa.
...

Resumo

  • Um Semaphore controla o acesso a recursos através de um contador de permissões, permitindo acesso concorrente limitado.
  • O método acquire() obtém uma permissão, bloqueando a thread se nenhuma estiver disponível.
  • O método release() devolve uma permissão ao semáforo, potencialmente liberando uma thread em espera.
  • É crucial usar um bloco try...finally para garantir que release() seja sempre chamado, prevenindo deadlocks.
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