¿Existe un mutex en Java?


110

¿Hay un objeto Mutex en Java o una forma de crear uno? Lo pregunto porque un objeto Semaphore inicializado con 1 permiso no me ayuda. Piense en este caso:

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

si ocurre una excepción en la primera adquisición, la liberación en el bloque de captura aumentará los permisos y el semáforo ya no es un semáforo binario.

¿Será la forma correcta?

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

¿El código anterior asegurará que el semáforo sea binario?


Mire el javadoc para java.util.concurrent.locks.AbstractQueuedSynchronizer. Tiene un ejemplo de cómo escribir una clase Mutex. -dbednar
joe

¿Descubrió este comportamiento empíricamente? ¿Es la implementación tal que la ejecución de release () en un semáforo de 1 permiso agrega un permiso adicional incluso si actualmente tiene otro?
Whimusical

Respuestas:



133

Cualquier objeto en Java se puede utilizar como bloqueo mediante un synchronizedbloque. Esto también se encargará automáticamente de liberar el bloqueo cuando ocurra una excepción.

Object someObject = ...;

synchronized (someObject) {
  ...
}

Puede leer más sobre esto aquí: Bloqueos intrínsecos y sincronización


Muy útil compra. Quería usar un semáforo.
Noam Nevo

11
@Noam: simplemente compare el código con el semáforo y synchronizedverá qué es mejor legible y menos propenso a errores.
Vlad

17
La palabra clave sincronizada no se puede utilizar si espera liberar el bloqueo con un método diferente (p transaction.begin(); transaction.commit(). Ej .).
Hosam Aly

y no está orientado a objetos ... tiene mucha sincronización de bajo nivel
anshulkatta

También mire someObject.wait(timeout)y someObject.notify()mientras mira el código de esta respuesta.
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
¿De qué manera es esto superior a las soluciones ya proporcionadas? ¿Cómo resuelve el problema que tenía el autor de la pregunta original?
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/… ... aunque tienes razón. La respuesta de Argv no ilustra ni explica estas operaciones.
FrustratedWithFormsDesigner

3
Este es un mutex recursivo que permitirá múltiples re-bloqueos del mismo hilo, lo que puede ser problemático. Un mutex básico "verdadero" (no recursivo, estilo C ++) permitiría solo un bloqueo a la vez. Si cambia su línea a private final ReentrantLock _mutex = ..., puede usar getHoldCount () para devolver el número de re-bloqueos de subprocesos. (Puede aplicar un Conditionpara evitar esto. Consulte la API ).
EntangledLoops

16

Nadie ha mencionado esto claramente, pero este tipo de patrón generalmente no es adecuado para semáforos. La razón es que cualquier hilo puede liberar un semáforo, pero normalmente solo desea que el hilo propietario que originalmente se bloqueó pueda desbloquearse . Para este caso de uso, en Java, usualmente usamos ReentrantLocks, que se puede crear así:

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

private final Lock lock = new ReentrantLock(true);

Y el patrón de diseño habitual de uso es:

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

Aquí hay un ejemplo en el código fuente de Java donde puede ver este patrón en acción.

Las cerraduras reentrantes tienen el beneficio adicional de apoyar la equidad.

Use semáforos solo si necesita una semántica que no sea de liberación de propiedad.


5
En realidad, esta debería ser la (única) respuesta correcta para esta pregunta. Explicación clara de las diferencias entre semáforo y bloqueo de exclusión mutua. Usar un semáforo con count=1no es un bloqueo de exclusión mutua.
Kaihua

3
Me alegro de que alguien lo haya señalado. Para el acceso exclusivo a un recurso, los mutex son el camino a seguir. Los semáforos binarios no son mutex, los semáforos deberían usarse más como mecanismo de señalización.
Shivam Tripathi

Rublo: ¿Entonces es, lockpor ejemplo, ReentrantLockun mutex? No estoy seguro de por qué mutexy binary semaphorese están equiparando a la misma entidad. Semaphorepuede ser liberado por cualquier hilo, por lo que puede no garantizar la protección critical section. ¿Alguna idea?
CuriousMind

@Kaihua: Resueno con tu pensamiento. Esta respuesta trae la diferencia clave
CuriousMind

6

Creo que deberías probar con:

Durante la inicialización del semáforo:

Semaphore semaphore = new Semaphore(1, true);

Y en tu Runnable Implementation

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

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

Así es como lo he hecho, pero no estoy muy seguro de si este es el camino a seguir.
Independiente el

1
De acuerdo con docs.oracle.com/javase/7/docs/api/java/util/concurrent/… "No hay ningún requisito de que un hilo que libera un permiso debe haber adquirido ese permiso llamando a adquirir. El uso correcto de un semáforo es establecido por convención de programación en la aplicación ". Si la adquisición arroja una excepción, la liberación en el final emitirá incorrectamente un permiso. Otros ejemplos en este hilo muestran el flujo correcto.
Brent K.

3

El error en la publicación original es el conjunto de llamadas adquisiciones () dentro del bucle try. Aquí hay un enfoque correcto para usar un semáforo "binario" (Mutex):

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

1

Para asegurarse de que a Semaphoresea ​​binario, solo debe asegurarse de pasar el número de permisos como 1 al crear el semáforo. Los Javadocs tienen un poco más de explicación.


1

El bloqueo de cada objeto es un poco diferente del diseño Mutex / Semaphore. Por ejemplo, no hay forma de implementar correctamente el desplazamiento de nodos vinculados liberando el bloqueo del nodo anterior y capturando el siguiente. Pero con mutex es 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();
} 

Actualmente, no existe tal clase en java.util.concurrent, pero puede encontrar la implementación de Mutext aquí Mutex.java . En cuanto a las bibliotecas estándar, Semaphore proporciona toda esta funcionalidad y mucho más.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.