web-dev-qa-db-fra.com

Qu'est-ce que "synchroniseurs propriétaires verrouillés" dans le vidage de thread?

J'essaie de comprendre à quoi fait référence Locked ownable synchronizers Dans un vidage de thread?

J'ai commencé à utiliser ReentrantReadWriteLock un thread dans l'état WAITING, en attendant un ReentrantReadWriteLock$FairSync Dans la liste des "synchroniseurs propriétaires verrouillés" d'un autre thread dans WAITING état (a ThreadPoolExecutor).

Je n'ai pas pu trouver beaucoup d'informations à ce sujet. S'agit-il d'une sorte de verrou "passé" sur le fil? J'essaie de comprendre d'où vient mon blocage et je ne vois aucun thread verrouiller activement ceux-ci (c'est-à-dire pas de - locked <0x...> Correspondant dans une trace de pile).

19
Matthieu

TL; DR: les verrous d'écriture apparaissent dans la liste des "synchroniseurs propriétaires", les verrous de lecture ne le font pas.

Je me suis retrouvé avec le MVCE suivant pour essayer de comprendre ce qui est avec "synchroniseur propriétaire". L'idée était d'avoir deux threads verrouillant/déverrouillant des verrous réentrants en lecture/écriture et de voir l'effet sur différents vidages de threads à différents moments (pris dans jVisualVM alors que le projet Eclipse était suspendu dans des points d'arrêt sur des lignes spécifiques).

Voici le code:

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();
        }
    }
}

Voici la sortie:

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)

Maintenant, les vidages de threads.

Le vidage de thread # 1 est effectué lorsque le thread "principal" a obtenu un verrou en lecture. Comme nous pouvons le voir, aucun "synchroniseur propriétaire" n'appartient au thread:

"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

Le vidage de thread # 2 est effectué après que le thread "autre" a pris le verrou d'écriture. Il apparaît dans les "synchroniseurs propriétaires":

"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)

Le vidage de thread # 3 est effectué après que le thread "autre" a libéré le verrou d'écriture (et est mort), et que le thread "principal" l'a pris:

"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)

Ainsi, les verrous en écriture apparaîtront dans la liste des "synchroniseurs propriétaires verrouillés", lorsque les verrous en lecture ne le seront pas. Même si getReadHoldCount() affiche le nombre de verrous de lecture pris par le thread en cours, un "verrouillage" en lecture ne semble pas appartenir à un thread particulier et est donc absent de la liste. Et cela rend difficile le débogage des interblocages (ou disons "pas aussi facile qu'avec jVisualVM").

EDIT: Pour aider à déterminer les erreurs de copier/coller avec des verrous pris et non libérés, comme dans:

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

vous pouvez utiliser la ligne de commande Linux suivante à la racine de votre répertoire source:

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

affichera combien de verrous de lecture sont pris, et:

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

affichera combien de verrous de lecture sont libérés. Si les chiffres ne correspondent pas, supprimez le | wc -l Pour afficher les détails des noms de fichiers (grep -H) Et du numéro de ligne (grep -n).

10
Matthieu

De documentation Java 7 :

Un synchroniseur propriétaire est un synchroniseur qui peut appartenir exclusivement à un thread et utilise AbstractOwnableSynchronizer (ou sa sous-classe) pour implémenter sa propriété de synchronisation. ReentrantLock et ReentrantReadWriteLock sont deux exemples de synchroniseurs propriétaires fournis par la plateforme.

7
AR1

Utilisation correcte ReentrantLock ce n'est pas aussi simple qu'il y paraît. Il comporte plusieurs écueils. Si nous parlons de blocages, je pense que vous devez savoir:

1.

La principale explication que nous avons trouvée à ce stade est associée à l'utilisation du verrou REentrantLock READ. Les verrous de lecture ne sont normalement pas conçus pour avoir une notion de propriété. Puisqu'il n'y a pas d'enregistrement sur lequel le thread détient un verrou de lecture, cela semble empêcher la logique du détecteur de blocage JVM HotSpot de détecter un blocage impliquant des verrous de lecture.

Certaines améliorations ont été mises en œuvre depuis lors, mais nous pouvons voir que la JVM ne peut toujours pas détecter ce scénario de blocage spécial.

Il vient de l'article de Nice " Concurrence Java: les blocages de thread cachés "

Si vous avez accès au code source la méthode getReadHoldCount () peut vous aider dans les blocages d'investigation.

2. Mise à niveau correcte de readLock vers writeLock - "Java ReentrantReadWriteLocks - comment acquérir en toute sécurité le verrouillage d'écriture?"

1
Vladislav Kysliy