Tengo un proceso A que contiene una tabla en la memoria con un conjunto de registros (recordA, recordB, etc ...)
Ahora, este proceso puede lanzar muchos subprocesos que afectan los registros y, a veces, podemos tener 2 subprocesos intentando acceder al mismo registro; esta situación debe ser negada. Específicamente, si un registro está BLOQUEADO por un hilo, quiero que el otro hilo se anule (no quiero BLOQUEAR o ESPERAR).
Actualmente hago algo como esto:
synchronized(record)
{
performOperation(record);
}
Pero esto me está causando problemas ... porque mientras Process1 está realizando la operación, si Process2 entra, bloquea / espera en la declaración sincronizada y cuando Process1 finaliza, realiza la operación. En cambio, 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á muy apreciada. Gracias,
fuente
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 esencialmente hace lo mismo que el
synchronized
mecanismo tradicional , pero con más funcionalidad.EDITAR: Como ha señalado Jon, el
isLocked()
método le dice en ese instante y, a partir de entonces, esa información está desactualizada. El método tryLock () proporcionará una operación más confiable (tenga en cuenta que también puede usar esto con un tiempo de espera)EDIT # 2: El ejemplo ahora incluye
tryLock()/unlock()
para mayor claridad.fuente
Encontré esto, podemos usarlo
Thread.holdsLock(Object obj)
para verificar si un objeto está bloqueado:Tenga en cuenta que
Thread.holdsLock()
regresafalse
si el bloqueo está retenido por algo y el hilo de llamada no es el hilo que mantiene el bloqueo.fuente
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 máquinas virtuales Java que no sean de Oracle y puede fallar en futuras versiones de VM ya que no es una API pública compatible.
He aquí 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 usar este enfoque solo si no puede refactorizar su código para usar objetos de bloqueo java.util.concurrent para esto y si está ejecutando en una VM de Oracle.
fuente
Si bien las respuestas de Lock son muy buenas, pensé en publicar una alternativa usando una estructura de datos diferente. Esencialmente, sus diversos hilos quieren 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 registros bloqueados al mismo tiempo en promedio, puede haber implicaciones de rendimiento con estas implementaciones. Un HashSet correctamente sincronizado también funcionaría y los bloqueos son breves.
Básicamente, el 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); }
Le evita tener que administrar un bloqueo por registro y proporciona un solo lugar en caso de que sea necesario borrar todos los bloqueos por alguna razón. Por otro lado, si alguna vez tiene más de un puñado de registros, un HashSet real con sincronización puede funcionar 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 subprocesamiento reales. Personalmente, usaría un Collections.synchronizedSet (new HashSet ()) porque será muy rápido ... la única implicación es que los subprocesos pueden ceder cuando de otra manera no lo harían.
fuente
Lock
supongo que podrías poner esto en tu propia clase.Otra solución es (en caso de que no haya tenido la oportunidad con las respuestas dadas aquí) está usando tiempos de espera. es decir, debajo de uno devolverá nulo después de 1 segundo colgado:
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; }
fuente
Gracias por esto, me ayudó a resolver una condición de carrera. Lo cambié un poco para usar cinturón y tirantes.
Así que aquí está mi sugerencia para UNA MEJORA de la respuesta aceptada:
Puede asegurarse de obtener acceso seguro al
tryLock()
método 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 evitaría la situación en la que podría recibir dos llamadas
tryLock()
al mismo tiempo, haciendo que la devolución sea potencialmente dudosa. Me gustaría ahora, si me equivoco, podría haber sido demasiado cauteloso. ¡Pero hey! Mi concierto es estable ahora :-) ..Lea más sobre mis problemas de desarrollo en mi Blog .
fuente