¿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?

Noam Nevo
fuente
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:

112

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

Tiene un patrón ligeramente diferente que es (creo) lo que estás buscando:

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

En este uso, solo está llamando release()después de una exitosaacquire()

Payne
fuente
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

casablanca
fuente
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();
Argv
fuente
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.

rublo
fuente
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);
}
Sashi Kant
fuente
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();
}
Sonríe
fuente
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.

Sanjay T. Sharma
fuente
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.

pogrebniuk
fuente