Estoy tratando de entender a qué se refiere Locked ownable synchronizers en un volcado de subprocesos?

Empecé a usar ReentrantReadWriteLock tiene un subproceso en estado WAITING, esperando un ReentrantReadWriteLock$FairSync en la lista "sincronizadores de propiedad bloqueados" de otro subproceso en estado WAITING (a ThreadPoolExecutor).

No pude encontrar mucha información sobre eso. ¿Es algún tipo de bloqueos "pasados" al hilo? Estoy tratando de averiguar de dónde viene mi punto muerto y no puedo ver ningún hilo que los bloquee activamente (es decir, no corresponde - locked <0x...> en ningún rastro de la pila).

27
Matthieu 23 dic. 2016 a las 14:23

3 respuestas

La mejor respuesta

TL; DR: los bloqueos de escritura aparecen en la lista de "sincronizadores apropiados", los bloqueos de lectura no .

Terminé con el siguiente MVCE para tratar de entender qué hay con el "sincronizador propietario". La idea era tener dos subprocesos bloqueando / desbloqueando bloqueos reentrantes de lectura / escritura y ver el efecto en diferentes volcados de subprocesos en diferentes tiempos (tomado en jVisualVM mientras el proyecto Eclipse estaba en pausa en puntos de interrupción en líneas específicas).

Aquí está el código:

package lock;

public class LockTest {

    static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

    public static void main(String[] args) {
        lock.readLock().lock();
        System.out.println(Thread.currentThread().getName()+": read hold "+lock.getReadHoldCount()+" read lock "+lock.getReadLockCount());
        new Th().start();
        synchronized (LockTest.class) {
            try { LockTest.class.wait(); } catch (InterruptedException e) { }
        }
        lock.readLock().unlock();
        System.out.println(Thread.currentThread().getName()+": unlocked read lock. Read hold "+lock.getReadHoldCount()+" read lock "+lock.getReadLockCount()+". Getting write lock");
        lock.writeLock().lock();
        System.out.println(Thread.currentThread().getName()+": got write lock. Unlocking (=>Thread dump #3)"); // Take thead dump #3 here ("main" has a write lock, "other" has died)
        lock.writeLock().unlock();
    }

    static class Th extends Thread {
        Th() { super("other"); }

        public void run() {
            System.out.println(Thread.currentThread().getName()+": read hold "+lock.getReadHoldCount()+" read lock "+lock.getReadLockCount());
            if (!lock.writeLock().tryLock())
                System.out.println(Thread.currentThread().getName()+": cannot lock write");
            else {
                System.out.println(Thread.currentThread().getName()+": lock write taken");
                lock.writeLock().unlock();
            }
            System.out.println(Thread.currentThread().getName()+": trying to unlock read lock");
            try {
                lock.readLock().unlock();
                System.out.println(Thread.currentThread().getName()+": successfully unlocked read lock. Read hold "+lock.getReadHoldCount()+" read lock "+lock.getReadLockCount());
            } catch (IllegalMonitorStateException e) {
                System.out.println(Thread.currentThread().getName()+": cannot unlock read lock: "+e.getMessage());
            }
            synchronized (LockTest.class) {
                System.out.println(Thread.currentThread().getName()+": notifying write lock take (=>Thread dump #1)");
                LockTest.class.notify(); // Take thead dump #1 here ("main" has a read lock)
            }
            System.out.println(Thread.currentThread().getName()+": locking write lock");
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName()+": unlocking write lock (=>Thread dump #2)"); // Take thead dump #2 here ("other" has a write lock)
            lock.writeLock().unlock();
        }
    }
}

Aquí está la salida:

main: read hold 1 read lock 1
other: read hold 0 read lock 1
other: cannot lock write
other: trying to unlock read lock
other: cannot unlock read lock: attempt to unlock read lock, not locked by current thread
other: notifying write lock take (=>Thread dump #1)
other: locking write lock
main: unlocked read lock. Read hold 0 read lock 0. Getting write lock
other: unlocking write lock (=>Thread dump #2)
main: got write lock. Unlocking (=>Thread dump #3)

Ahora, los volcados de hilos.

El volcado de subproceso # 1 se toma cuando el subproceso "principal" tiene un bloqueo de lectura. Como podemos ver, ningún "sincronizador apropiado" es propiedad del hilo :

"main" prio=10 tid=0x00007fea5c00d000 nid=0x1866 in Object.wait() [0x00007fea65bd5000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007acf62620> (a java.lang.Class for lock.LockTest)
    at java.lang.Object.wait(Object.java:503)
    at lock.LockTest.main(LockTest.java:14)
    - locked <0x00000007acf62620> (a java.lang.Class for lock.LockTest)

   Locked ownable synchronizers:
    - None

"other" prio=10 tid=0x00007fea5c0e0800 nid=0x1883 at breakpoint[0x00007fea3abe8000]
   java.lang.Thread.State: RUNNABLE
    at lock.LockTest$Th.run(LockTest.java:46)
    - locked <0x00000007acf62620> (a java.lang.Class for lock.LockTest)

   Locked ownable synchronizers:
    - None

El volcado del hilo # 2 se toma después de que el hilo "otro" ha tomado el bloqueo de escritura. Aparece en los "sincronizadores que se pueden usar":

"main" prio=10 tid=0x00007fea5c00d000 nid=0x1866 waiting on condition [0x00007fea65bd5000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000007acf63278> (a java.util.concurrent.locks.ReentrantReadWriteLock$FairSync)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:867)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1197)
    at java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock.lock(ReentrantReadWriteLock.java:945)
    at lock.LockTest.main(LockTest.java:18)

   Locked ownable synchronizers:
    - None

"other" prio=10 tid=0x00007fea5c0e0800 nid=0x1883 at breakpoint[0x00007fea3abe8000]
   java.lang.Thread.State: RUNNABLE
    at lock.LockTest$Th.run(LockTest.java:51)

   Locked ownable synchronizers:
    - <0x00000007acf63278> (a java.util.concurrent.locks.ReentrantReadWriteLock$FairSync)

El volcado de subprocesos n. ° 3 se toma después de que el subproceso "otro" liberó el bloqueo de escritura (y murió) y el subproceso "principal" lo tomó:

"main" prio=10 tid=0x00007fea5c00d000 nid=0x1866 at breakpoint[0x00007fea65bd5000]
   java.lang.Thread.State: RUNNABLE
    at lock.LockTest.main(LockTest.java:19)

   Locked ownable synchronizers:
    - <0x00000007acf63278> (a java.util.concurrent.locks.ReentrantReadWriteLock$FairSync)

Por lo tanto, los bloqueos de escritura aparecerán en la lista de "sincronizadores de propiedad bloqueados", cuando los bloqueos de lectura no lo harán. Aunque getReadHoldCount() muestra el número de bloqueos de lectura tomados por el hilo actual, un "bloqueo" de lectura no parece pertenecer a un hilo particular y, por lo tanto, está ausente de la lista. Y eso dificulta la depuración de puntos muertos (o digamos "no tan fácil como con jVisualVM").

EDITAR: Para ayudar a descubrir errores de copiar / pegar con bloqueos tomados y no liberados, como en:

myLock.readLock().lock();
try {
    // ...
} finally {
    myLock.readLock().lock(); // Oops! Should be "unlock()"
}

Puede usar la siguiente línea de comando de Linux en la raíz de su directorio fuente:

find . -name '*.java' -exec grep -Hn 'myLock.readLock().lock();' {} \; | wc -l

Mostrará cuántos bloqueos de lectura se toman y:

find . -name '*.java' -exec grep -Hn 'myLock.readLock().unlock();' {} \; | wc -l

Mostrará cuántos bloqueos de lectura se liberan . Si los números no coinciden, elimine el | wc -l para mostrar los detalles de los nombres de archivo (grep -H) y el número de línea (grep -n).

11
Matthieu 19 mar. 2019 a las 11:05

De documentación de Java 7:

Un sincronizador propietario es un sincronizador que puede ser exclusivamente propiedad de un hilo y usa AbstractOwnableSynchronizer (o su subclase) para implementar su propiedad de sincronización. ReentrantLock y ReentrantReadWriteLock son dos ejemplos de sincronizadores propios proporcionado por la plataforma.

7
AR1 23 dic. 2016 a las 12:09

Uso correcto ReentrantLock no es tan fácil como parece. Tiene varias trampas. Si hablamos de puntos muertos creo que necesitas saber:

1.

La explicación principal que encontramos en este punto está asociada con el uso del bloqueo ReentrantLock READ. Los bloqueos de lectura normalmente no están diseñados para tener una noción de propiedad. Dado que no hay un registro de qué subproceso contiene un bloqueo de lectura, esto parece evitar que la lógica del detector de punto muerto HotSpot JVM detecte un punto muerto que involucra bloqueos de lectura.

Desde entonces, se implementaron algunas mejoras, pero podemos ver que la JVM aún no puede detectar este escenario de bloqueo especial.

Es del buen artículo "concurrencia de Java: los puntos muertos ocultos del hilo "

Si tiene acceso al código fuente getReadHoldCount () puede ayudar en la investigación de puntos muertos.

2. Actualización correcta de readLock a writeLock - "Java ReentrantReadWriteLocks: ¿cómo adquirir de forma segura el bloqueo de escritura?"

1
marc_s 2 jul. 2018 a las 20:52