Praticando concorrência em Java! – Semáforos

Como falado no post anterior sobre concorrência em java, o problema do Produtor/Consumidor pode ser resolvido utilizando um monitor que bloqueia o recurso, permitindo apenas um acesso de cada vez.

Desta vez vamos comentar um pouco sobre semáforos, que permitem um número maior de Thread acessando os recursos.

Semáforos

Um semáforo é uma estrutura de dados que controle o acesso de aplicações aos recursos, baseando-se em um número inteiro, que representa a quantidade de acessos que podem ser feitos. Assim utilizamos semáforos para controlar a quantidade de acesso a determinado recurso.

Na API do Java existe uma implementação de semáforos que fazem justamente esse controle. Vamos ver a seguir um exemplo que usa o Semáforo nativo do Java.

Utilizando semáforos

Primeiro vamos definir uma implementação de Thread que vai utilizar o semáforo.

public class ProcessadorThread extends Thread {
	private int idThread;
	private Semaphore semaforo;

	public ProcessadorThread(int id, Semaphore semaphore) {
		this.idThread = id;
		this.semaforo = semaphore;
	}
}

Definimos inicialmente um identificador para a nossa Thread e uma referência a um semáforo que irá controlar o acesso a essas variáveis.

Agora vamos definir os métodos da nossa Thread, dentro da classe ProcessadorThread:

	private void processar() {
		try {
			System.out.println("Thread #" + idThread + " processando");
			Thread.sleep((long) (Math.random() * 10000));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

Este método processar() apenas faz a thread dormir por algum tempo, simulando o efeito de um processamento longo.

	private void entrarRegiaoNaoCritica() {
		System.out.println("Thread #" + idThread + " em região não crítica");
		processar();
	}

Este método simula o acesso da Thread em uma região não crítica, ou seja, uma região ao qual não é necessário pedir uma trava. Exibimos o atual estado da Thread, para facilitar o entendimento do progama, e realizamos um processamento qualquer.

	private void entrarRegiaoCritica() {
		System.out.println("Thread #" + idThread
				+ " entrando em região crítica");
		processar();
		System.out.println("Thread #" + idThread + " saindo da região crítica");
	}

Este outro método será utilizado para simular o acesso da Thread em uma região crítica. Ele será chamado logo após conseguir a trava do semáforo.

	public void run() {
		entrarRegiaoNaoCritica();
		try {
			semaforo.acquire();
			entrarRegiaoCritica();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			semaforo.release();
		}
	}

E finalmente, como nossa classe extende o comportamento de uma Thread, nós sobrecarregamos o método run() que será chamado quando a Thread iniciar. Neste método nós realizamos um processamento não crítico, depois requisitamos o acesso ao semáforo (com o semaforo.acquire()) e em seguida realizamos o processamento de uma região crítica. Por fim, liberamos o recurso do semáforo (com o semaforo.release()).

Entendendo um pouco mais sobre semáforos

Vamos agora falar um pouco de teoria sobre semáforos. Todo semáforo deve possuir dois métodos: P e V, que têm sua origem das palavras parsen (passar) e e vrygeren (liberar). Esta definição de semáforo foi proposta por Dijkstra para evitar o tão temido DeadLock.

Quando se quer requisitar o recurso, faz-se uma chamada ao método P, que verifica se é possível liberar o recurso. Ao terminar, faz-se uma chamada ao método V, que notifica as outras Thread que o recurso foi liberado.

Na implementação do Java, o método “acquire()” faz o papel do método P e o método “release()” faz o papel do método V. Vamos analisar o método main e ver como são utilizados os semáforos:

	public static void main(String[] args) {
		int numeroDePermicoes = 2;
		int numeroDeProcessos = 6;
		Semaphore semaphore = new Semaphore(numeroDePermicoes);
		ProcessadorThread[] processos = new ProcessadorThread[numeroDeProcessos];
		for (int i = 0; i < numeroDeProcessos; i++) {
			processos[i] = new ProcessadorThread(i, semaphore);
			processos[i].start();
		}
	}

Para construir um semáforo precisamos informar o número máximo de Thread que podem acessar o recurso ao mesmo tempo. No nosso exemplo, apenas duas Thread poderão entrar na região crítica. Executando o programa a saída no console será algo do tipo:

Thread #1 em região não crítica
Thread #1 processando
Thread #0 em região não crítica
Thread #0 processando
Thread #5 em região não crítica
Thread #5 processando
Thread #3 em região não crítica
Thread #3 processando
Thread #2 em região não crítica
Thread #2 processando
Thread #4 em região não crítica
Thread #4 processando
Thread #0 entrando em região crítica
Thread #0 processando
Thread #4 entrando em região crítica
Thread #4 processando
Thread #4 saindo da região crítica
Thread #5 entrando em região crítica
Thread #5 processando
Thread #5 saindo da região crítica
Thread #3 entrando em região crítica
Thread #3 processando
Thread #0 saindo da região crítica
Thread #1 entrando em região crítica
Thread #1 processando
Thread #1 saindo da região crítica
Thread #2 entrando em região crítica
Thread #2 processando
Thread #3 saindo da região crítica
Thread #2 saindo da região crítica

Como já comentamos, no Java os Thread são acordados aleatoriamente, então a saída não será a mesma. O que é realmente importante notar é que nunca temos mais que duas Thread na região crítica.

A Thread 0 e 4 entram na área crítica e, somente quando a Thread 4 libera o recurso, a Thread 5 entra na região.

No próximo post vamos ver como implementar um semáforo próprio para resolver o problema do Produtor/Consumidor.

Obrigado pela visita, espero que este post tenha ajudado. Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Encontrou algum erro no código? Comente também. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 🙂

Referências:

[1] JENKOV, Jakob. Semaphore. Disponível em: http://tutorials.jenkov.com/java-concurrency/semaphores.html. Acesso em: 25 set. 2011.
[2] LIMA J., José; FREITAS, Veronice de. Semáforos. Disponível em: http://www.jr.eti.br/mestrado/cmp041/semaforos.html. Acesso em: 25 set. 2011.

Anúncios

15 comentários sobre “Praticando concorrência em Java! – Semáforos

  1. Olá, nós tentamos compilar esse código e deu alguns errinhos… o que fizemos de errado? Pode nos explicar?

  2. /*
    * Concorrência de Threads, Semáforo. Exemplo.
    */
    package testes;

    import java.util.concurrent.Semaphore;

    /**
    *
    * @author Marcos Brizeno
    */
    public class ProcessadorThread extends Thread {
    private int idThread;
    private Semaphore semaforo;

    public ProcessadorThread(int id, Semaphore semaphore) {
    this.idThread = id;
    this.semaforo = semaphore;
    }

    private void processar() {
    try {
    System.out.println(“Thread #” + idThread + ” processando”);
    Thread.sleep((long) (Math.random() * 10000));
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    private void entrarRegiaoNaoCritica() {
    System.out.println(“Thread #” + idThread + ” em região não crítica”);
    processar();
    }

    private void entrarRegiaoCritica() {
    System.out.println(“Thread #” + idThread
    + ” entrando em região crítica”);
    processar();
    System.out.println(“Thread #” + idThread + ” saindo da região crítica”);
    }

    public void run() {
    entrarRegiaoNaoCritica();
    try {
    semaforo.acquire();
    entrarRegiaoCritica();
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    semaforo.release();
    }
    }

    public static void main(String[] args) {
    int numeroDePermicoes = 2;
    int numeroDeProcessos = 6;
    Semaphore semaphore = new Semaphore(numeroDePermicoes);
    ProcessadorThread[] processos = new ProcessadorThread[numeroDeProcessos];
    for (int i = 0; i < numeroDeProcessos; i++) {
    processos[i] = new ProcessadorThread(i, semaphore);
    processos[i].start();
    }
    }
    }

  3. gostei bastante da linguagem utilizada na explicação, tudo foi feito de forma clara e objetiva. parabéns

  4. muito bom! foi bastante proveitoso. 😀

  5. Cara como é que duas threads podem acessar o mesmo recurso compartilhado?
    Nem testei esse codigo mas sua compilacao:

    Thread #0 entrando em região crítica
    Thread #0 processando
    Thread #4 entrando em região crítica
    Thread #4 processando
    Thread #4 saindo da região crítica

    a thread 0 e a 4 entram em na regiao critica… e ai?

  6. Valeu ai Marcos, salvo minha pele num trabalho de Inteligência Artificial! Estava tendo problemas com o “synchronized”…
    A partir de agora só semáforos! 🙂

  7. Marcos, por favor, em ajude !!!
    Eu estou fazendo um projeto sobre a materia concorrente e distribuida, daí peguei esses código seu e fiz o meu, porém eu tenho que salvar o tempo, só que não da pra fazer a soma porque toda vez que executa o run as variaveis zeram, pode me ajudar ?

    O RUN EH ESSE

    public void run() {
    entrarRegiaoNaoCritica();
    try {
    semaforo.acquire();
    entrarRegiaoCritica();
    vetor = criarVetor.criarVetorAleatorio();
    System.out.println(“Vetor : “+ Arrays.toString(vetor));
    tempoInicial = System.currentTimeMillis();
    merge.mergeSort(vetor);
    tempoFinal = System.currentTimeMillis();
    System.out.println(“Vetor Ordenado: “+ Arrays.toString(vetor));
    this.tempos = (tempoFinal – tempoInicial)/1000;
    JOptionPane.showMessageDialog(null,”———————\n”
    + “Algoritmo Concorrente\n”
    + “———————\n”
    + “Tempo ” +this.thread+ “: ” +deci.format(this.tempos)+” segundos”);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    semaforo.release();
    }
    }

    Eu tenho que somar o this.tempos, porém, ele não soma, só pega o valor do último tempo zerado, o que está errado ? ME AJUDE

    • Não sei se entendi muito bem, mas vamos lá. A única razão para o this.tempos está vindo vazio é se o tempo inicial e final forem os mesmo, tenta colocar um sysout ou algo do tipo pra debugar.
      Se entendi bem, você tá executando esse código em um laço, para várias execuções, certo? Se for o caso tem que fazer this.tempos += … só o = vai sempre sobre-escrever o this.tempos.
      Espero ter ajudado.

  8. Obrigada e parabéns pela ótima explicação!

  9. Muito obrigada pela ajuda!

  10. Muito bom! Apenas uma colaboração…
    A classe ProcessadorThread para que não ocorra problemas no main é necessario que seja static, portanto o código correto seria.
    public static class ProcessadorThread extends Thread {

    }
    Mas isso não tira o mérito do post, que por sinal muito bem escrito e exemplificado. Parabéns!

  11. Muito boa explicação, só um detalhe o certo é “Permissões”

  12. Boa tarde Marcos,

    comecei a efetuar um curso de Java e tentei testar este código mas o mesmo esta gerando um erro poderia me ajudar.

    processos[i] = new ProcessadorThread(i, semaphore);

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s