Esiste un Mutex in Java?


110

Esiste un oggetto Mutex in java o un modo per crearne uno? Lo chiedo perché un oggetto Semaphore inizializzato con 1 permesso non mi aiuta. Pensa a questo caso:

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

se si verifica un'eccezione alla prima acquisizione, il rilascio nel blocco catch aumenterà i permessi e il semaforo non è più un semaforo binario.

Sarà il modo corretto?

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

Il codice precedente garantirà che il semaforo sarà binario?


Guarda il javadoc per java.util.concurrent.locks.AbstractQueuedSynchronizer. Ha un esempio di come scrivere una classe Mutex. -dbednar
joe

Hai scoperto questo comportamento empiricamente? L'implementazione è tale che l'esecuzione di release () su un semaforo con permesso 1 aggiunge un permesso extra anche se ne ha attualmente un altro, davvero?
Whimusical

Risposte:



133

Qualsiasi oggetto in Java può essere utilizzato come blocco utilizzando un synchronizedblocco. Questo provvederà anche automaticamente a rilasciare il blocco quando si verifica un'eccezione.

Object someObject = ...;

synchronized (someObject) {
  ...
}

Puoi leggere di più su questo qui: Intrinsic Locks and Synchronization


Acquisto molto utile, volevo usare un semaforo.
Noam Nevo

11
@ Noam: confronta il codice con il semaforo e con synchronized, vedrai cosa è meglio leggibile e meno soggetto a errori.
Vlad

17
La parola chiave sincronizzata non può essere utilizzata se prevedi di rilasciare il blocco in un metodo diverso (ad esempio transaction.begin(); transaction.commit()).
Hosam Aly

e non è orientato agli
oggetti..sua

Guarda anche dentro someObject.wait(timeout)e someObject.notify()mentre stai guardando il codice di questa risposta.
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();

5
In che modo questo è superiore alle soluzioni già fornite? Come risolve il problema riscontrato dal richiedente originale?
Martin,

@ Martin:, "Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements."da: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… ... anche se hai ragione. La risposta di Argv non illustra o spiega queste operazioni.
FrustratedWithFormsDesigner

3
Questo è un mutex ricorsivo che consentirà più blocchi dallo stesso thread, il che può essere problematico. Un mutex "vero" di base (non ricorsivo, in stile C ++) consentirebbe solo un blocco alla volta. Se modifichi la tua riga in private final ReentrantLock _mutex = ..., puoi usare getHoldCount () per restituire il numero di thread re-lock. (Potresti applicare una Conditionper impedirlo. Consulta l'API .)
EntangledLoops

16

Nessuno lo ha menzionato chiaramente, ma questo tipo di pattern di solito non è adatto per i semafori. La ragione è che qualsiasi thread può rilasciare un semaforo, ma di solito si desidera solo il thread proprietario che originariamente bloccata per essere in grado di sbloccare . Per questo caso d'uso, in Java, di solito usiamo ReentrantLocks, che può essere creato in questo modo:

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

private final Lock lock = new ReentrantLock(true);

E il solito modello di utilizzo del design è:

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

Ecco un esempio nel codice sorgente java in cui puoi vedere questo modello in azione.

Le serrature rientranti hanno l'ulteriore vantaggio di supportare l'equità.

Usa i semafori solo se hai bisogno di una semantica senza rilascio di proprietà.


5
In realtà questa dovrebbe essere la (unica) risposta corretta a questa domanda. Chiara spiegazione delle differenze tra semaforo e blocco di mutua esclusione. L'uso di un semaforo con count=1non è un blocco di mutua esclusione.
Kaihua

3
Sono contento che qualcuno l'abbia fatto notare. Per l'accesso esclusivo a una risorsa, i mutex sono la strada da percorrere. I semafori binari non sono mutex, i semafori dovrebbero essere usati più come meccanismo di segnalazione.
Shivam Tripathi

Rublo: Quindi, lockad esempio, è ReentrantLockun mutex? Non sono sicuro del perché mutexe binary semaphorevengono considerati identificati come la stessa entità. Semaphorepuò essere rilasciato da qualsiasi thread, quindi ciò potrebbe non garantire la protezione critical section. qualche idea?
CuriousMind

@Kaihua: risuono del tuo pensiero. Questa risposta porta la differenza fondamentale
CuriousMind

6

Penso che dovresti provare con:

Durante l'inizializzazione del semaforo:

Semaphore semaphore = new Semaphore(1, true);

E nel tuo Runnable Implementation

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

} 
catch (Exception e) 
{
// Logging
}
finally
{
   semaphore.release(1);
}

È così che l'ho fatto, ma non sono del tutto sicuro se questa sia la strada da percorrere.
Distaccato

1
Secondo docs.oracle.com/javase/7/docs/api/java/util/concurrent/… "Non è necessario che un thread che rilascia un permesso debba aver acquisito quel permesso chiamando acquisire. L'uso corretto di un semaforo è stabilito dalla convenzione di programmazione nell'applicazione. " Se l'acquisizione genera un'eccezione, il rilascio nell'ultimo rilascerà erroneamente un permesso. Altri esempi in questo thread mostrano il flusso corretto.
Brent K.

3

L'errore nel post originale è la chiamata acquis () impostata all'interno del ciclo try. Ecco un approccio corretto per utilizzare il semaforo "binario" (Mutex):

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

1

Per assicurarti che a Semaphoresia binario devi solo assicurarti di passare il numero di permessi come 1 quando crei il semaforo. I Javadoc hanno qualche spiegazione in più.


1

Il blocco di ogni oggetto è leggermente diverso dal design Mutex / Semaphore. Ad esempio, non è possibile implementare correttamente l'attraversamento dei nodi collegati rilasciando il blocco del nodo precedente e catturando quello successivo. Ma con mutex è facile implementare:

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();
} 

Attualmente, non esiste una classe di questo tipo java.util.concurrent, ma puoi trovare l'implementazione di Mutext qui Mutex.java . Per quanto riguarda le librerie standard, Semaphore fornisce tutte queste funzionalità e molto altro ancora.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.