2009-11-22 26 views
56

Tengo un proceso A que contiene una tabla en la memoria con un conjunto de registros (recordA, recordB, etc ...)¿Cómo se determina si un objeto está bloqueado (sincronizado) para no bloquearlo en Java?

Ahora, este proceso puede iniciar muchos hilos que afectan los registros, y a veces podemos tener 2 hilos que intentan acceder al mismo registro: esta situación debe denegarse. Específicamente, si un registro está BLOQUEADO por un hilo, quiero que el otro hilo se cierre (no quiero BLOQUEAR ni ESPERAR).

Actualmente hago algo como esto:

synchronized(record) 
{ 
performOperation(record); 
} 

Pero esto es lo que hizo problemas ... porque mientras Process1 está realizando la operación, si viene en Proceso2 bloquea/espera en el estado sincronizado y cuando Process1 terminado, realiza la operación. En su lugar quiero algo como esto:

if (record is locked) 
    return; 

synchronized(record) 
{ 
performOperation(record); 
} 

¿Alguna pista sobre cómo se puede lograr esto? Cualquier ayuda sería muy apreciada. Gracias,

Respuesta

61

Una cosa a tener en cuenta es que el instantáneo recibe tal información, es obsoleto. En otras palabras, se le puede decir que nadie tiene el candado, pero cuando intenta adquirirlo, bloquea porque otro hilo quitó el candado entre el cheque y usted tratando de adquirirlo.

Brian tiene razón al punto en el Lock, pero creo que lo que realmente quiere es su tryLock método:

Lock lock = new ReentrantLock(); 
...... 
if (lock.tryLock()) 
{ 
    // Got the lock 
    try 
    { 
     // Process record 
    } 
    finally 
    { 
     // Make sure to unlock so that we don't cause a deadlock 
     lock.unlock(); 
    } 
} 
else 
{ 
    // Someone else had the lock, abort 
} 

También puede llamar tryLock con una cantidad de tiempo para esperar - por lo que podría tratar de adquirir por una décima de segundo, luego abortar si no puede obtenerlo (por ejemplo).

(Creo que es una pena que la API de Java no tenga, por lo que tengo entendido, la misma funcionalidad para el bloqueo "integrado", como la clase Monitor en .NET. , hay muchas otras cosas que no me gustan en ambas plataformas cuando se trata de subprocesos: ¡cada objeto que potencialmente tiene un monitor, por ejemplo!)

+0

Sí. Ese es un buen punto. Tomé el código de ejemplo literalmente, mientras que el anterior es definitivamente una implementación más robusta –

+4

¿Pero cómo uso un bloqueo por registro? Actualmente, los registros se almacenan en una HashTable de registros ... ¿entonces necesito una Hashtable de Locks que coincida? Estoy tratando de asegurar que tenga la mayor simultaneidad posible, así que si un proceso quiere acceder a recordC debería estar bien (si solo está bloqueado el registroB) - Utilizo un BLOQUEO global entonces es esencialmente lo mismo que bloquear todo el hashtable . ... ¿tiene sentido? – Shaitan00

+0

@ Shaitan00: la manera más fácil sería tener un bloqueo dentro del registro. Básicamente, desea un bloqueo asociado a cada registro, así que colóquelo en el objeto. –

23

Eche un vistazo a los objetos Lock introducidos en los paquetes de simultaneidad de Java 5.

p. Ej.

Lock lock = new ReentrantLock() 
if (lock.tryLock()) { 
    try { 
     // do stuff using the lock... 
    } 
    finally { 
     lock.unlock(); 
    } 
} 
    ... 

El objeto ReentrantLock está haciendo esencialmente el mismo que el tradicional mecanismo de synchronized, pero con una mayor funcionalidad.

EDIT: Como ha señalado Jon, el método isLocked() le dice al ese instante, y después de que la información no está actualizado. El método tryLock() dará un funcionamiento más confiable (tenga en cuenta que también puede utilizarlo con un tiempo de espera)

EDIT # 2: El ejemplo ahora incluye tryLock()/unlock() para mayor claridad.

3

Mientras que las respuestas de Lock son muy buenas, pensé en publicar una alternativa usando una estructura de datos diferente Esencialmente, sus diversos hilos desean saber qué registros están bloqueados y cuáles no.Una forma de hacerlo es realizar un seguimiento de los registros bloqueados y asegurarse de que la estructura de datos tenga las operaciones atómicas correctas para agregar registros al conjunto bloqueado.

Usaré CopyOnWriteArrayList como ejemplo porque es menos "mágico" para la ilustración. CopyOnWriteArraySet es una estructura más apropiada. Si tiene muchos y muchos registros bloqueados al mismo tiempo en promedio, entonces puede haber implicaciones de rendimiento con estas implementaciones. Un HashSet correctamente sincronizado también funcionaría y los bloqueos son breves.

Básicamente, código de uso se vería así:

CopyOnWriteArrayList<Record> lockedRecords = .... 
... 
if (!lockedRecords.addIfAbsent(record)) 
    return; // didn't get the lock, record is already locked 

try { 
    // Do the record stuff 
}   
finally { 
    lockedRecords.remove(record); 
} 

Se le evita tener que gestionar un bloqueo por registro y proporciona un único lugar debe despejando todas las cerraduras ser necesario por alguna razón. Por otro lado, si alguna vez tiene más que un puñado de registros, un HashSet real con sincronización puede hacerlo mejor ya que las búsquedas de agregar/eliminar serán O (1) en lugar de lineales.

Solo una forma diferente de ver las cosas. Solo depende de cuáles sean sus requisitos de roscado reales. Personalmente, usaría un Collections.synchronizedSet (nuevo HashSet()) porque será realmente rápido ... la única implicación es que los hilos pueden ceder cuando de otro modo no tendrían.

+0

Si quieres tener control total, podrías poner esto en tu propia clase 'Lock', supongo. – bvdb

8

Si bien el enfoque anterior con un objeto de bloqueo es la mejor manera de hacerlo, si tiene que poder verificar el bloqueo con un monitor, se puede hacer. Sin embargo, viene con una advertencia de salud ya que la técnica no es portátil para las máquinas virtuales Java que no son de Oracle y puede romperse en futuras versiones de máquinas virtuales ya que no es una API pública compatible.

Aquí es cómo hacerlo:

private static sun.misc.Unsafe getUnsafe() { 
    try { 
     Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); 
     field.setAccessible(true); 
     return (Unsafe) field.get(null); 
    } catch (Exception e) { 
     throw new RuntimeException(e); 
    } 
} 

public void doSomething() { 
    Object record = new Object(); 
    sun.misc.Unsafe unsafe = getUnsafe(); 
    if (unsafe.tryMonitorEnter(record)) { 
    try { 
     // record is locked - perform operations on it 
    } finally { 
     unsafe.monitorExit(record); 
    } 
    } else { 
     // could not lock record 
    } 
} 

Mi consejo sería utilizar este método sólo si no se puede refactorizar el código para utilizar java.util.concurrent bloquear objetos para esto y si se están ejecutando en una máquina virtual de Oracle.

+12

@alestanis, no estoy de acuerdo Aquí estoy leyendo estas respuestas hoy y estoy feliz por todas las respuestas/comentarios, no importa cuándo se den. – Tom

+0

@Tom Estas respuestas están marcadas por el sistema SO, por eso dejé un mensaje. Verás cuando comiences a revisar :) – alestanis

+2

@alestanis, creo que es desafortunado: a menudo veo viejas preguntas que tienen respuestas aceptadas, pero que también tienen respuestas nuevas y mejores, ya sea porque la tecnología cambia o porque la respuesta aceptada no era del todo correcta. – Tom

5

me encontré con esto, podemos utilizar Thread.holdsLock(Object obj) para comprobar si un objeto está bloqueado:

devoluciones true si y sólo si el hilo actual mantiene el bloqueo del monitor en el objeto especificado.

+4

Tenga en cuenta que [Thread.holdsLock()] (https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#holdsLock (java.lang.Object)) devuelve falso si el se mantiene el bloqueo y el hilo de llamada no es el hilo que contiene el bloqueo. –

+0

La página vinculada está mal escrita y tiene demasiada publicidad en ella. Lo evitaría a menos que estés usando un bloqueador de anuncios y no te importe tropezar con una mala gramática. – datguy

0

Otra solución alternativa (en caso de que no haya tenido oportunidad con las respuestas dadas aquí) es el uso de tiempos de espera. es decir, por debajo de una devolverá un valor nulo después de 1 segundo para colgar:

ExecutorService executor = Executors.newSingleThreadExecutor(); 
     //create a callable for the thread 
     Future<String> futureTask = executor.submit(new Callable<String>() { 
      @Override 
      public String call() throws Exception { 
       return myObject.getSomething(); 
      } 
     }); 

     try { 
      return futureTask.get(1000, TimeUnit.MILLISECONDS); 
     } catch (InterruptedException | ExecutionException | TimeoutException e) { 
      //object is already locked check exception type 
      return null; 
     } 
-1

Gracias por esto, que me ayudó a cabo la resolución de una condición de carrera. Lo cambié un poco para usar cinturón y tirantes.

Así que aquí es mi sugerencia para la mejora de la respuesta aceptada:

Puede asegurarse de que obtiene un acceso seguro al método tryLock() haciendo algo como esto:

Lock localLock = new ReentrantLock(); 

    private void threadSafeCall() { 
    boolean isUnlocked = false; 

    synchronized(localLock) { 
     isUnlocked = localLock.tryLock(); 
    } 

    if (isUnlocked) { 
     try { 
     rawCall(); 
     } 
     finally { 
     localLock.unlock(); 
     } 
    } else { 
     LOGGER.log(Level.INFO, "THANKS! - SAVED FROM DOUBLE CALL!"); 
    } 
    } 

Esto haría evite la situación en la que puede recibir dos llamadas al tryLock() casi al mismo tiempo, lo que ocasiona que la devolución sea potencialmente dudosa. Me gustaría ahora si estoy equivocado, podría ser más prudente aquí. ¡Pero hey! Mi actuación ahora es estable :-) ..

Lea más sobre mis problemas de desarrollo en mi Blog.

+0

usando sincronizado en un objeto de bloqueo es muy, muy malo. El objetivo de un bloqueo es evitar la sincronización, por lo que puede esperar o regresar rápidamente cuando no puede obtener el bloqueo. – Ajax

Cuestiones relacionadas