Pourquoi utiliser un ReentrantLock si on peut utiliser synchronisé (this)?

J’essaie de comprendre ce qui rend le locking en concurrence si important si on peut utiliser synchronized (this) . Dans le code factice ci-dessous, je peux faire soit:

  1. synchroniser la méthode entière ou synchroniser la zone vulnérable (synchronisée (this) {…})
  2. OU verrouillez la zone de code vulnérable avec un ReentrantLock.

Code:

  private final ReentrantLock lock = new ReentrantLock(); private static List ints; public Integer getResult(Ssortingng name) { . . . lock.lock(); try { if (ints.size()==3) { ints=null; return -9; } for (int x=0; x>>>"+ints.get(x)); } } finally { lock.unlock(); } return random; } 

    Un ReentrantLock est non structuré , contrairement aux constructions synchronized – c.-à-d. Que vous n’avez pas besoin d’utiliser une structure de bloc pour le locking et que vous pouvez même verrouiller les méthodes. Un exemple:

     private ReentrantLock lock; public void foo() { ... lock.lock(); ... } public void bar() { ... lock.unlock(); ... } 

    Un tel stream est impossible à représenter via un seul moniteur dans une construction synchronized .


    En dehors de cela, ReentrantLock prend en charge l’ interrogation des verrous et les interruptions interruptibles qui prennent en charge le délai d’attente . ReentrantLock également en charge les règles d’ équité configurables , ce qui permet une programmation plus flexible des threads.

    Le constructeur de cette classe accepte un paramètre d’ équité facultatif. Lorsque cette valeur est définie sur true , les verrous favorisent l’access au thread qui attend le plus longtemps. Sinon, ce verrou ne garantit aucun ordre d’access particulier. Les programmes utilisant des lockings corrects accessibles par de nombreux threads peuvent afficher un débit global inférieur (c.-à-d. Plus lent, souvent beaucoup plus lent) que ceux utilisant le paramètre par défaut. Notez toutefois que l’équité des verrous ne garantit pas l’équité de la planification des threads. Ainsi, l’un des nombreux threads utilisant un verrou équitable peut l’obtenir plusieurs fois de suite, alors que les autres threads actifs ne progressent pas et ne sont pas actuellement en possession du verrou. Notez également que la méthode tryLock ne respecte pas le paramètre d’équité. Il réussira si le verrou est disponible même si d’autres threads attendent.


    ReentrantLock peut également être plus évolutif , ReentrantLock bien meilleurs résultats en cas de conflit. Vous pouvez en savoir plus à ce sujet ici .

    Cette réclamation a été contestée, cependant; voir le commentaire suivant:

    Dans le test de locking réentrant, un nouveau verrou est créé à chaque fois, il n’y a donc pas de locking exclusif et les données résultantes sont invalides. De plus, le lien IBM n’offre aucun code source pour le benchmark sous-jacent, il est donc impossible de déterminer si le test a été effectué correctement.


    Quand faut-il utiliser ReentrantLock ? Selon cet article de developerWorks …

    La réponse est assez simple: utilisez-la lorsque vous avez besoin de quelque chose qu’elle fournit, mais pas synchronized , comme l’attente de locking temporisé, l’attente de locking interruptible, les verrous non structurés en blocs, les variables de condition multiples ou l’interrogation de verrou. ReentrantLock présente également des avantages en ReentrantLock évolutivité, et vous devriez l’utiliser si vous vous trouvez dans une situation qui présente un fort conflit, mais rappelez-vous que la grande majorité des blocs synchronized ne présentent pratiquement aucun conflit, et encore moins de conflits. Je vous conseille de développer avec la synchronisation jusqu’à ce que la synchronisation se soit révélée inadéquate, plutôt que de simplement supposer que “les performances seront meilleures” si vous utilisez ReentrantLock . Rappelez-vous que ce sont des outils avancés pour les utilisateurs avancés. (Et les utilisateurs vraiment avancés ont tendance à préférer les outils les plus simples qu’ils peuvent trouver jusqu’à ce qu’ils soient convaincus que les outils simples sont inadéquats).

    Un verrou réentrant permettra au détenteur de la serrure d’entrer des blocs de code même après avoir obtenu le verrou en entrant d’autres blocs de code. Un verrou non réentrant aurait le bloc porte-verrou sur lui-même car il devrait libérer le verrou obtenu à partir d’un autre bloc de code pour obtenir le même verrou pour entrer dans le bloc nested nécessitant un bloc de code.

      public synchronized void functionOne() { // do something functionTwo(); // do something else // redundant, but permitted... synchronized(this) { // do more stuff } } public synchronized void functionTwo() { // do even more stuff! } 

    Les fonctionnalités étendues du locking réentrant incluent: –

    1. La possibilité d’avoir plus d’une variable de condition par moniteur. Les moniteurs utilisant le mot-clé synchronisé ne peuvent en avoir qu’un. Cela signifie que les verrous réentrants prennent en charge plusieurs files d’attente wait () / notify ().
    2. La possibilité de rendre la serrure “équitable”. “Les verrous [équitables] favorisent l’access au thread qui attend le plus longtemps. Sinon, ce verrou ne garantit aucun ordre d’access particulier.” Les blocs synchronisés sont injustes.
    3. La possibilité de vérifier si le verrou est maintenu.
    4. La possibilité d’obtenir la liste des threads en attente sur le verrou.

    Les inconvénients des serrures réentrantes sont: –

    Besoin d’append une déclaration d’importation. Vous devez envelopper les acquisitions de locking dans un bloc try / finally. Cela le rend plus laid que le mot-clé synchronisé. Le mot-clé synchronisé peut être placé dans des définitions de méthode, ce qui évite la nécessité d’un bloc qui réduit l’imbrication.

    Quand utiliser :-

    1. ReentrantLock pourrait être plus approprié si vous devez implémenter un thread qui traverse une liste liée, verrouillant le nœud suivant et déverrouillant le nœud actuel.
    2. Le mot-clé synchronisé est approprié dans une situation telle que le grossissement des verrous, fournit une rotation adaptative, un locking biaisé et le potentiel d’élision du verrou via une parsing d’échappement. Ces optimisations ne sont actuellement pas implémentées pour ReentrantLock.

    Pour plus d’ informations

    ReentrantReadWriteLock est un verrou spécialisé alors que synchronized(this) est un verrou à usage général. Ils sont similaires mais pas tout à fait pareils.

    Vous avez raison de dire que vous pouvez utiliser synchronized(this) au lieu de ReentrantReadWriteLock mais l’inverse n’est pas toujours vrai.

    Si vous souhaitez mieux comprendre ce qui rend ReentrantReadWriteLock spécial, recherchez des informations sur la synchronisation des threads producteur-consommateur.

    En général, vous pouvez vous rappeler que la synchronisation de méthodes entières et la synchronisation générale (en utilisant le mot-clé synchronized ) peuvent être utilisées dans la plupart des applications sans trop penser à la sémantique de la synchronisation. explorer d’autres mécanismes de synchronisation plus précis ou spécifiques.

    Soit dit en passant, l’utilisation de synchronized(this ) – et en général le locking à l’aide d’une instance de classe publique – peut poser problème car cela ouvre des codes à des lockings potentiels. programme.

    De la page de documentation d’ Oracle sur ReentrantLock :

    Un locking mutuel d’exclusion réentrant avec le même comportement de base et la même sémantique que le locking de moniteur implicite auquel on accède en utilisant des méthodes et des instructions synchronisées, mais avec des fonctionnalités étendues.

    1. Un ReentrantLock appartient au dernier thread qui a été correctement verrouillé, mais ne l’a pas encore déverrouillé. Un thread invoquant le verrou reviendra, réussissant à acquérir le verrou, lorsque le verrou n’appartient pas à un autre thread. La méthode retournera immédiatement si le thread en cours possède déjà le verrou.

    2. Le constructeur de cette classe accepte un paramètre d’ équité facultatif. Lorsque cette valeur est définie sur true, les verrous favorisent l’access au thread qui attend le plus longtemps . Sinon, ce verrou ne garantit aucun ordre d’access particulier.

    Fonctionnalités clés de ReentrantLock conformément à cet article

    1. Possibilité de verrouiller de manière interruptible.
    2. Possibilité d’expiration en attendant le locking.
    3. Puissance pour créer un verrou équitable.
    4. API pour obtenir la liste des threads en attente de locking.
    5. Flexibilité d’essayer de verrouiller sans bloquer.

    Vous pouvez utiliser ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock pour acquérir davantage de contrôle sur le locking granulaire des opérations de lecture et d’écriture.

    Regardez cet article de Benjamen sur l’utilisation de différents types de ReentrantLocks

    Vous pouvez utiliser des verrous réentrants avec une stratégie d’équité ou un délai d’expiration pour éviter la famine du thread. Vous pouvez appliquer une politique d’équité des threads. cela aidera à éviter un fil qui attend pour toujours pour accéder à vos ressources.

     private final ReentrantLock lock = new ReentrantLock(true); //the param true turns on the fairness policy. 

    La “politique d’équité” choisit le prochain thread exécutable à exécuter. Il est basé sur la priorité, le temps écoulé depuis la dernière exécution, bla bla

    De plus, Synchroniser peut bloquer indéfiniment s’il ne peut pas échapper au bloc. Reentrantlock peut avoir un délai d’attente défini.