Existe um Mutex em Java?

110

Existe um objeto Mutex em java ou uma maneira de criar um? Estou perguntando porque um objeto Semaphore inicializado com 1 licença não me ajuda. Pense neste caso:

try {
   semaphore.acquire();
   //do stuff
   semaphore.release();
} catch (Exception e) {
   semaphore.release();
}

se ocorrer uma exceção na primeira aquisição, a liberação no bloco catch aumentará as permissões e o semáforo não será mais um semáforo binário.

Será a maneira correta?

try {
   semaphore.acquire();
   //do stuff
} catch (Exception e) {
   //exception stuff
} finally {
   semaphore.release();
}

O código acima garantirá que o semáforo será binário?

Noam Nevo
fonte
Consulte o javadoc para java.util.concurrent.locks.AbstractQueuedSynchronizer. Ele tem um exemplo de como escrever uma classe Mutex. -dbednar
joe
Você descobriu esse comportamento empiricamente? A implementação é tal que a execução de release () em um Semaphore de 1 licença adiciona uma licença extra, mesmo se estiver segurando outra, na verdade?
Whimusical 01 de

Respostas:

112

Veja esta página: http://www.oracle.com/technetwork/articles/javase/index-140767.html

Tem um padrão ligeiramente diferente que é (eu acho) o que você está procurando:

try {
  mutex.acquire();
  try {
    // do something
  } finally {
    mutex.release();
  }
} catch(InterruptedException ie) {
  // ...
}

Nesse uso, você só está ligando release()depois de umacquire()

Payne
fonte
133

Qualquer objeto em Java pode ser usado como um bloqueio usando um synchronizedbloco. Isso também irá automaticamente liberar o bloqueio quando ocorrer uma exceção.

Object someObject = ...;

synchronized (someObject) {
  ...
}

Você pode ler mais sobre isso aqui: Bloqueios intrínsecos e sincronização

Casablanca
fonte
Muito útil comprar eu queria usar um semáforo.
Noam Nevo
11
@Noam: basta comparar o código com o semáforo e com o synchronized, você verá o que é mais legível e menos sujeito a erros.
Vlad,
17
A palavra-chave synchronized não pode ser usada se você espera liberar o bloqueio em um método diferente (por exemplo transaction.begin(); transaction.commit()).
Hosam Aly
e não é orientado a objetos
.. é
Além disso, observe someObject.wait(timeout)e someObject.notify()enquanto analisa o código desta resposta.
Daniel F
25
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


private final Lock _mutex = new ReentrantLock(true);

_mutex.lock();

// your protected code here

_mutex.unlock();
Argv
fonte
5
De que forma isso é superior às soluções já fornecidas? Como isso resolve o problema que o autor da pergunta original estava tendo?
Martin
@Martin:, "Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements."de: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… ... embora você tenha razão . A resposta de Argv não ilustra nem explica essas operações.
FrustratedWithFormsDesigner
3
Este é um mutex recursivo que permitirá múltiplos bloqueios do mesmo thread, o que pode ser problemático. Um mutex básico "verdadeiro" (não recursivo, estilo C ++) permitiria apenas um bloqueio de cada vez. Se você alterar sua linha para private final ReentrantLock _mutex = ..., pode usar getHoldCount () para retornar o número de bloqueios de thread. (Você pode aplicar um Conditionpara evitar isso. Consulte a API .)
EntangledLoops
16

Ninguém mencionou isso claramente, mas esse tipo de padrão geralmente não é adequado para semáforos. O motivo é que qualquer thread pode liberar um semáforo, mas normalmente você só deseja que o thread do proprietário que foi bloqueado originalmente seja desbloqueado . Para este caso de uso, em Java, geralmente usamos ReentrantLocks, que pode ser criado assim:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

private final Lock lock = new ReentrantLock(true);

E o padrão de design usual de uso é:

  lock.lock();
  try {
      // do something
  } catch (Exception e) {
      // handle the exception
  } finally {
      lock.unlock();
  }

Aqui está um exemplo no código-fonte java onde você pode ver esse padrão em ação.

Os bloqueios reentrantes têm o benefício adicional de apoiar a justiça.

Use semáforos apenas se precisar de uma semântica de liberação sem propriedade.

rublo
fonte
5
Na verdade, esta deve ser a (única) resposta correta para esta pergunta. Explicação clara das diferenças entre semáforo e bloqueio de exclusão mútua. Usar um semáforo com count=1não é um bloqueio de exclusão mútua.
Kaihua,
3
Que bom que alguém apontou. Para acesso exclusivo a um recurso, os mutexes são o caminho a percorrer. Os semáforos binários não são mutex, os semáforos devem ser usados ​​mais como mecanismo de sinalização.
Shivam Tripathi
Ruble: Então, lockpor exemplo, é ReentrantLockum mutex? Não sei por que mutexe binary semaphoreestão sendo considerados a mesma entidade. Semaphorepode ser liberado por qualquer thread, de modo que pode não garantir proteção critical section. Alguma ideia?
CuriousMind
@Kaihua: Eu me identifico com seu pensamento. Esta resposta traz a principal diferença
CuriousMind
6

Eu acho que você deveria tentar com:

Durante a inicialização do Semaphore:

Semaphore semaphore = new Semaphore(1, true);

E no seu Runnable Implementation

try 
{
   semaphore.acquire(1);
   // do stuff

} 
catch (Exception e) 
{
// Logging
}
finally
{
   semaphore.release(1);
}
Sashi Kant
fonte
Foi assim que fiz, mas não tenho certeza se esse é o caminho a percorrer.
Separado de
1
De acordo com docs.oracle.com/javase/7/docs/api/java/util/concurrent/… "Não há nenhuma exigência de que um thread que libera uma licença deva ter adquirido essa licença chamando adquirir. O uso correto de um semáforo é estabelecido pela convenção de programação no aplicativo. " Se a aquisição lançar uma exceção, a liberação no finalmente liberará incorretamente uma licença. Outros exemplos neste tópico mostram o fluxo correto.
Brent K.
3

O erro na postagem original é a chamada purchase () definida dentro do loop try. Aqui está uma abordagem correta para usar semáforo "binário" (Mutex):

semaphore.acquire();
try {
   //do stuff
} catch (Exception e) {
   //exception stuff
} finally {
   semaphore.release();
}
sorria
fonte
1

Para garantir que a Semaphoreseja binário, você só precisa passar o número de licenças como 1 ao criar o semáforo. Os Javadocs têm um pouco mais de explicação.

Sanjay T. Sharma
fonte
1

O bloqueio de cada objeto é um pouco diferente do design Mutex / Semaphore. Por exemplo, não há maneira de implementar corretamente o cruzamento de nós vinculados com a liberação do bloqueio do nó anterior e a captura do próximo. Mas com mutex é fácil de implementar:

Node p = getHead();
if (p == null || x == null) return false;
p.lock.acquire();  // Prime loop by acquiring first lock.
// If above acquire fails due to interrupt, the method will
//   throw InterruptedException now, so there is no need for
//   further cleanup.
for (;;) {
Node nextp = null;
boolean found;
try { 
 found = x.equals(p.item); 
 if (!found) { 
   nextp = p.next; 
   if (nextp != null) { 
     try {      // Acquire next lock 
                //   while still holding current 
       nextp.lock.acquire(); 
     } 
     catch (InterruptedException ie) { 
      throw ie;    // Note that finally clause will 
                   //   execute before the throw 
     } 
   } 
 } 
}finally {     // release old lock regardless of outcome 
   p.lock.release();
} 

Atualmente, não existe essa classe em java.util.concurrent, mas você pode encontrar a implementação de Mutext aqui Mutex.java . Quanto às bibliotecas padrão, o Semaphore fornece todas essas funcionalidades e muito mais.

Pogrebniuk
fonte