inmovilizar - ¿Cómo se determina si un objeto está bloqueado(sincronizado) para no bloquearlo en Java?
jdialog java (7)
Tengo un proceso A que contiene una tabla en la memoria con un conjunto de registros (registro A, registro B, etc.)
Ahora, este proceso puede iniciar muchos hilos que afectan los registros, y algunas veces podemos tener 2 hilos tratando de acceder al mismo registro, esta situación debe ser denegada. 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 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ía muy apreciada. Gracias,
Eche un vistazo a los objetos de 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 básicamente está haciendo lo mismo que el mecanismo synchronized
tradicional, pero con más funcionalidad.
EDITAR: Como Jon notó, el método isLocked()
te dice en ese instante , y después de eso esa información está desactualizada. El método tryLock() dará un funcionamiento más confiable (tenga en cuenta que puede usar esto también con un tiempo de espera).
EDIT # 2: El ejemplo ahora incluye tryLock()/unlock()
para mayor claridad.
Encontré esto, podemos usar Thread.holdsLock(Object obj)
para verificar si un objeto está bloqueado:
Devuelve
true
si y solo si el hilo actual contiene el bloqueo del monitor en el objeto especificado.
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 de MEJORAR la respuesta aceptada:
Puede asegurarse de tener 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 evitaría la situación en la que podría obtener dos llamadas a tryLock()
casi al mismo tiempo, lo que ocasionaría que el retorno esté lleno de dudas. 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 .
Otra solución alternativa (en caso de que no haya tenido oportunidad con las respuestas dadas aquí) es utilizar tiempos de espera. es decir, debajo de uno se devolverá nulo después de 1 segundo colgando:
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;
}
Si bien el enfoque anterior que utiliza un objeto de bloqueo es la mejor manera de hacerlo, si tiene que poder verificar el bloqueo con un monitor, puede hacerlo. 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í está 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 máquina virtual de Oracle.
Si bien las respuestas de Lock son muy buenas, pensé en publicar una alternativa con 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, 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 impide tener que gestionar un bloqueo por registro y proporciona un único lugar, por alguna razón debería ser necesario borrar todos los bloqueos. 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.
Una cosa a tener en cuenta es que en el instante en que recibes dicha información, queda obsoleta. 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 señalar a Lock
, pero creo que lo que realmente quieres es su método tryLock
:
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 a tryLock
con una cantidad de tiempo de espera, por lo que podría tratar de adquirirlo durante una décima de segundo y luego cancelar si no puede obtenerlo (por ejemplo).
(Creo que es una pena que la API de Java no tenga, por lo que yo sé, la misma funcionalidad para el bloqueo "integrado", como la clase Monitor
en .NET. Por otra parte, hay muchas de otras cosas que no me gustan en ambas plataformas cuando se trata de enhebrar, ¡por ejemplo, cada objeto que potencialmente tiene un monitor!)