CodeGym /Blogue Java /Random-PT /Diferença entre um mutex, um monitor e um semáforo
John Squirrels
Nível 41
San Francisco

Diferença entre um mutex, um monitor e um semáforo

Publicado no grupo Random-PT
Oi! Quando você estudou multithreading no CodeGym, frequentemente encontrou os conceitos de "mutex" e "monitor". Sem espiar, você pode dizer como eles diferem? :) Se sim, muito bem! Se não (isso é mais comum), não é surpresa. "Mutex" e "monitor" são na verdade conceitos relacionados. Além disso, ao ler as lições e assistir a vídeos sobre multithreading em outros sites, você encontrará outro conceito semelhante: "semáforo". Ele também tem uma função muito semelhante aos monitores e mutexes. É por isso que vamos investigar esses três termos. Veremos alguns exemplos e chegaremos a uma compreensão definitiva de como esses conceitos diferem entre si :)

mutex

Um mutex (ou bloqueio) é um mecanismo especial para sincronizar threads. Um é "anexado" a cada objeto em Java — você já sabe disso :) Não importa se você usa classes padrão ou cria suas próprias classes, por exemplo, Cat e Dog : todos os objetos de todas as classes têm um mutex . O termo "mutex" vem de "EXclusão MÚTUA", que descreve perfeitamente o seu propósito. Como dissemos em uma de nossas lições anteriores, um mutex permite garantir que apenas uma thread por vez tenha acesso ao objeto. Um exemplo popular da vida real de um mutex envolve banheiros. Quando uma pessoa entra em uma divisória do banheiro, ela tranca a porta por dentro. O banheiro é como um objeto que pode ser acessado por vários fios. A fechadura na porta da divisória é como um mutex, e a fila de pessoas do lado de fora representa os fios. A fechadura da porta é o mutex do banheiro: garante que apenas uma pessoa possa entrar. Qual é a diferença entre um mutex, um monitor e um semáforo?  - 2Em outras palavras, apenas um thread por vez pode trabalhar com recursos compartilhados. As tentativas de outros encadeamentos (pessoas) de obter acesso a recursos ocupados falharão. Um mutex tem vários recursos importantes. Primeiro , apenas dois estados são possíveis: "desbloqueado" e "bloqueado". Isso nos ajuda a entender como funciona: você pode traçar paralelos com variáveis ​​booleanas (verdadeiro/falso) ou números binários (0/1). , o estado não pode ser controlado diretamente. Java não tem nenhum mecanismo que permita explicitamente pegar um objeto, obter seu mutex e atribuir o status desejado. Em outras palavras, você não pode fazer algo como:

Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
Isso significa que você não pode liberar o mutex de um objeto. Somente a máquina Java tem acesso direto a ele. Os programadores trabalham com mutexes por meio das ferramentas da linguagem.

Monitor

Um monitor é uma "superestrutura" adicional sobre um mutex. Na verdade, um monitor é um pedaço de código "invisível" para o programador. Quando falamos sobre mutexes anteriormente, demos um exemplo simples:

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...some logic, available for all threads

       synchronized (obj) {

           // Logic available to just one thread at a time
       }
   }
}
No bloco de código marcado com a palavra-chave sincronizar , o mutex de nosso objeto obj é adquirido. Ótimo, podemos adquirir a fechadura, mas como exatamente a "proteção" é fornecida? Quando vemos a palavra sincronizada , o que impede que as outras threads entrem no bloco? A proteção vem de um monitor! O compilador converte a palavra-chave sincronizada em vários pedaços especiais de código. Mais uma vez, vamos retornar ao nosso exemplo com o método doSomething() . Acrescentaremos a ele:

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...some logic, available for all threads

       // Logic available to just one thread at a time
       synchronized (obj) {

           /* Do important work that requires that the object
           be accessed by only one thread */
           obj.someImportantMethod();
       }
   }
}
Aqui está o que acontece "sob o capô" depois que o compilador converte este código:

public class Main {

   private Object obj = new Object();

   public void doSomething() throws InterruptedException {

       // ...some logic, available for all threads

       // Logic available to just one thread at a time:
     
       /* as long as the object's mutex is busy,
       all the other threads (except the one that acquired it) are put to sleep */
       while (obj.getMutex().isBusy()) {
           Thread.sleep(1);
       }

       // Mark the object's mutex as busy
       obj.getMutex().isBusy() = true;

       /* Do important work that requires that the object
       be accessed by only one thread */
       obj.someImportantMethod();

       // Free the object's mutex
       obj.getMutex().isBusy() = false;
   }
}
Claro, este não é um exemplo real. Aqui, usamos código semelhante ao Java para descrever o que acontece dentro da máquina Java. Dito isto, este pseudo-código dá uma excelente compreensão do que realmente acontece com o objeto e as threads dentro do bloco sincronizado e como o compilador converte esta palavra-chave em várias instruções que são "invisíveis" para o programador. Basicamente, o Java usa a palavra-chave sincronizada para representar um monitor . Todo o código que aparece no lugar da palavra-chave sincronizada no último exemplo é o monitor.

Semáforo

Outra palavra que você encontrará em seu estudo pessoal de multithreading é "semáforo". Vamos descobrir o que é isso e como ele difere de um monitor e de um mutex. Um semáforo é uma ferramenta para sincronizar o acesso a algum recurso. Sua característica distintiva é que ele usa um contador para criar o mecanismo de sincronização. O contador nos diz quantos threads podem acessar simultaneamente o recurso compartilhado. Qual é a diferença entre um mutex, um monitor e um semáforo?  - 3Os semáforos em Java são representados pela classe Semaphore . Ao criar objetos semáforos, podemos usar os seguintes construtores:

Semaphore(int permits)
Semaphore(int permits, boolean fair)
Passamos o seguinte para o construtor:
    int permite — o valor inicial e máximo do contador. Em outras palavras, esse parâmetro determina quantos encadeamentos podem acessar simultaneamente o recurso compartilhado;
  • boolean fair — estabelece a ordem na qual as threads terão acesso. Se justo for verdadeiro, o acesso será concedido aos encadeamentos em espera na ordem em que eles o solicitaram. Se for falso, a ordem é determinada pelo escalonador de encadeamento.
Um exemplo clássico de uso de semáforos é o problema do filósofo jantando. Qual é a diferença entre um mutex, um monitor e um semáforo?  - 4Para facilitar o entendimento, vamos simplificar um pouco. Imagine que temos 5 filósofos que precisam almoçar. Além disso, temos uma mesa que pode acomodar simultaneamente no máximo duas pessoas. Nossa tarefa é alimentar todos os filósofos. Nenhum deles deve passar fome e nenhum deles deve "bloquear" um ao outro ao tentar se sentar à mesa (devemos evitar o impasse). Aqui está como nossa aula de filósofo se parecerá:

class Philosopher extends Thread {

   private Semaphore sem;

   // Did the philosopher eat?
   private boolean full = false;

   private String name;

   Philosopher(Semaphore sem, String name) {
       this.sem=sem;
       this.name=name;
   }

   public void run()
   {
       try
       {
           // If the philosopher has not eaten
           if (!full) {
               // Ask the semaphore for permission to run
               sem.acquire();
               System.out.println(name + " takes a seat at the table");

               // The philosopher eats
               sleep(300);
               full = true;

               System.out.println(name + " has eaten! He leaves the table");
               sem.release();

               // The philosopher leaves, making room for others
               sleep(300);
           }
       }
       catch(InterruptedException e) {
           System.out.println("Something went wrong!");
       }
   }
}
E aqui está o código para executar nosso programa:

public class Main {

   public static void main(String[] args) {

       Semaphore sem = new Semaphore(2);
       new Philosopher(sem, "Socrates").start();
       new Philosopher(sem,"Plato").start();
       new Philosopher(sem,"Aristotle").start();
       new Philosopher(sem, "Thales").start();
       new Philosopher(sem, "Pythagoras").start();
   }
}
Criamos um semáforo cujo contador é definido como 2 para satisfazer a condição: apenas dois filósofos podem comer ao mesmo tempo. Ou seja, apenas duas threads podem ser executadas ao mesmo tempo, porque nossa classe Philosopher herda Thread ! Os métodos aquire() e release() da classe Semaphore controlam seu contador de acesso. O método Acquire() pede ao semáforo acesso ao recurso. Se o contador for >0, o acesso é concedido e o contador é reduzido em 1. O método release()o método "libera" o acesso anteriormente concedido, retornando-o ao contador (aumenta em 1 o contador de acessos do semáforo). O que obtemos quando executamos o programa? O problema está resolvido? Nossos filósofos não lutarão enquanto esperam sua vez? :) Aqui está a saída do console que obtivemos:

Socrates takes a seat at the table 
Plato takes a seat at the table 
Socrates has eaten! He leaves the table 
Plato has eaten! He leaves the table 
Aristotle takes a seat at the table 
Pythagoras takes a seat at the table 
Aristotle has eaten! He leaves the table 
Pythagoras has eaten! He leaves the table 
Thales takes a seat at the table 
Thales has eaten! He leaves the table 
Conseguimos! E embora Thales tenha jantado sozinho, acho que não o ofendemos :) Você deve ter notado algumas semelhanças entre um mutex e um semáforo. Na verdade, eles têm a mesma missão: sincronizar o acesso a algum recurso. Qual é a diferença entre um mutex, um monitor e um semáforo?  - 5A única diferença é que o mutex de um objeto pode ser adquirido por apenas uma thread por vez, enquanto no caso de um semáforo, que utiliza um contador de threads, várias threads podem acessar o recurso simultaneamente. Isso não é apenas uma coincidência :) Um mutex é na verdade um semáforocom uma contagem de 1. Em outras palavras, é um semáforo que pode acomodar um único thread. Também é conhecido como "semáforo binário" porque seu contador pode ter apenas 2 valores - 1 ("desbloqueado") e 0 ("bloqueado"). É isso! Como você pode ver, afinal não é tão confuso :) Agora, se você quiser estudar multithreading com mais detalhes na Internet, será um pouco mais fácil para você navegar nesses conceitos. Nos vemos nas próximas aulas!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION