web-dev-qa-db-fra.com

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

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

  1. synchronisé toute la méthode ou synchroniser la zone vulnérable (synchronized(this){...})
  2. OU verrouillez la zone de code vulnérable avec un ReentrantLock.

Code:

    private final ReentrantLock lock = new ReentrantLock(); 
    private static List<Integer> ints;

    public Integer getResult(String name) { 
        .
        .
        .
        lock.lock();
        try {
            if (ints.size()==3) {
                ints=null;
                return -9;
            }   

            for (int x=0; x<ints.size(); x++) {
                System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
            }

        } finally {
            lock.unlock();
        } 
        return random;
}
298
adhg

Un ReentrantLock est non structuré , contrairement aux constructions synchronized - vous n'avez donc pas besoin d'utiliser une structure de bloc pour le verrouillage et vous pouvez même tenir un verrou sur les méthodes. Un exemple:

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

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


En plus de cela, ReentrantLock prend en charge interrogation de verrouillage et le verrou interruptible attend que le délai d’attente de prise en charge . ReentrantLock prend également en charge politique configurable d'équité , permettant une planification plus souple des threads.

Le constructeur de cette classe accepte un paramètre optionnel fairness . Lorsque le paramètre true est mis en conflit, les verrous favorisent l’octroi de l’accès au thread le plus longtemps en attente. Sinon, ce verrouillage ne garantit aucune commande d'accès particulière. Les programmes utilisant des verrous justes auxquels accèdent plusieurs threads peuvent afficher un débit global plus faible (c'est-à-dire plus lents; souvent beaucoup plus lent) que ceux utilisant le paramètre par défaut, mais présentent des variances plus faibles en termes de temps pour obtenir des verrous et garantir l'absence de famine. Notez cependant 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 d'autres threads actifs ne progressent pas et ne détiennent pas actuellement le verrou. Notez également que la méthode tryLock sans heure ne respecte pas le paramètre d'équité. Cela réussira si le verrou est disponible même si d'autres threads sont en attente.


ReentrantLock peut également être plus évolutif , performant beaucoup mieux sous conflit. Vous pouvez en lire plus à ce sujet ici .

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

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


Quand devriez-vous utiliser ReentrantLocks? Selon cet article de developerWorks ...

La réponse est assez simple: utilisez-la lorsque vous avez réellement besoin de quelque chose, car elle indique que synchronized ne le fait pas, comme l'attente de verrouillage temporisé, l'attente de verrouillage interruptible, les verrous non structurés en blocs, les variables de condition multiples ou l'interrogation de verrous. ReentrantLock présente également des avantages en termes d'évolutivité, et vous devez l'utiliser si vous vous trouvez dans une situation très conflictuelle, mais souvenez-vous que la grande majorité des blocs synchronized ne présentent presque jamais de conflit, encore moins de conflit élevé. Je conseillerais de développer avec la synchronisation jusqu'à ce que la synchronisation se révèle inadéquate, plutôt que de supposer que "les performances seront meilleures" si vous utilisez ReentrantLock. Rappelez-vous, ce sont des outils avancés pour les utilisateurs avancés. (Et les véritables utilisateurs avancés ont tendance à préférer les outils les plus simples qu’ils puissent trouver jusqu’à ce qu’ils soient convaincus que les outils simples sont inadéquats.) Comme d’habitude, réglez le problème en premier, puis craignez de le rendre plus rapide ou non.

446
oldrinb

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

Vous avez raison de dire que vous pouvez utiliser synchronized(this) au lieu de ReentrantReadWriteLock, mais le contraire n'est pas toujours vrai.

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

En général, vous pouvez vous rappeler que la synchronisation de méthode complète et la synchronisation à usage général (à l'aide du mot clé synchronized) peuvent être utilisées dans la plupart des applications sans trop réfléchir à la sémantique de la synchronisation, mais si vous devez extraire les performances de votre code, vous devrez peut-être explorer d'autres mécanismes de synchronisation plus détaillés ou plus spécialisés.

En passant, utiliser synchronized(this) - et verrouiller en général à l'aide d'une instance de classe publique - peut poser problème car cela ouvre votre code à d'éventuelles impasses, car quelqu'un d'autre non averti pourrait essayer de verrouiller votre objet ailleurs le programme.

13
Mike Dinescu

De la page de documentation Oracle sur ReentrantLock :

Une exclusion mutuelle réentrante verrouille avec le même comportement de base et la même sémantique que le verrou de surveillance implicite auquel on a accès à l'aide de méthodes et d'instructions synchronisées, mais avec des capacités étendues.

  1. Un ReentrantLock appartient au dernier thread qui se verrouille avec succès, mais ne le déverrouille pas encore. Un verrou appelant un thread retournera, acquérant le verrou avec succès, lorsque le verrou n'appartient à aucun 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 optionnel fairness . Lorsque la valeur est true, en conflit, les verrous favorisent l’octroi d’un accès au thread le plus long . Sinon, ce verrouillage ne garantit aucune commande d'accès particulière.

ReentrantLock caractéristiques principales selon article

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

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

Jetez un coup d'œil à ceci article par Benjamen sur l'utilisation de différents types de ReentrantLocks

8
Ravindra babu

Vous pouvez utiliser des verrous réentrants avec une stratégie d'équité ou un délai d'attente pour éviter la privation de threads. Vous pouvez appliquer une politique d'équité de threads. cela évitera qu'un fil d'attente attende à jamais pour accéder à vos ressources.

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

La "politique d'équité" sélectionne 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, Synchronize peut bloquer indéfiniment s’il ne peut pas sortir du bloc. Reentrantlock peut avoir un délai d'attente défini.

0
j2emanue

Supposons que ce code est en cours d'exécution dans un thread:

private static ReentrantLock lock = new ReentrantLock();

void accessResource() {
    lock.lock();
    if( checkSomeCondition() ) {
        accessResource();
    }
    lock.unlock();
}

Parce que le thread possède le verrou, il autorisera plusieurs appels à verrouiller (), de sorte qu'il entre à nouveau dans le verrou. Cela peut être réalisé avec un compte de référence, de sorte qu'il ne soit pas nécessaire d'acquérir de nouveau le verrou.

0
RonTLV

Une chose à garder à l'esprit est:

Le nom ' ReentrantLock ' affiche un message erroné concernant un autre mécanisme de verrouillage indiquant qu'ils ne sont pas ré-entrants. Ce n'est pas vrai. Le verrou acquis via 'synchronisé' est également ré-entrant en Java.

La principale différence est que "synchronisé" utilise un verrou intrinsèque (celui de chaque objet), contrairement à l'API de verrouillage.

0
Summer

Verrous synchronisés n'offre aucun mécanisme de file d'attente dans lequel, après l'exécution d'un thread, tout thread exécuté en parallèle peut acquérir le verrou. En raison de quoi le thread qui est là dans le système et qui fonctionne pendant une longue période de temps n'a jamais la chance d'accéder à la ressource partagée, conduisant ainsi à la famine.

Les verrous réentrants sont très flexibles et ont une politique d'équité dans laquelle si un thread attend plus longtemps et après l'achèvement du fil en cours d'exécution, nous pouvons nous assurer que le fil en attente plus long a la chance d’accès à la ressource partagée, réduisant ainsi le débit du système et le rendant plus fastidieux.

0
Sumit Kapoor