java.util.concurrent
API java.util.concurrent
fournit une classe appelée Lock
, qui sérialiserait le contrôle afin d’accéder à la ressource critique. Il donne des méthodes telles que park()
et unpark()
.
Nous pouvons faire des choses similaires si nous pouvons utiliser synchronized
mot-clé synchronized
et utiliser les méthodes wait()
et notify() notifyAll()
.
Je me demande lequel des deux est le meilleur en pratique et pourquoi?
Si vous verrouillez simplement un object, je préférerais utiliser synchronized
Exemple:
Lock.acquire(); doSomethingNifty(); // Throws a NPE! Lock.release(); // Oh noes, we never release the lock!
Vous devez explicitement try{} finally{}
partout.
Alors qu’avec synchronisé, c’est super clair et impossible de se tromper:
synchronized(myObject) { doSomethingNifty(); }
Cela dit, Lock
s peut être plus utile pour les choses plus compliquées où vous ne pouvez pas acquérir et libérer de manière aussi nette. Je préférerais honnêtement éviter d’utiliser en premier lieu bare Lock
, et opter pour un contrôle de concurrence plus sophistiqué tel que CyclicBarrier
ou LinkedBlockingQueue
, s’ils répondent à vos besoins.
Je n’ai jamais eu de raison d’utiliser wait()
ou notify()
mais il y en a peut-être de bons.
Je me demande lequel des deux est le meilleur en pratique et pourquoi?
J’ai constaté que Lock
and Condition
(et d’autres nouvelles classes concurrent
) ne sont que des outils supplémentaires pour la boîte à outils. Je pouvais faire tout ce dont j’avais besoin avec mon vieux marteau à griffes (le mot-clé synchronized
), mais c’était difficile à utiliser dans certaines situations. Plusieurs de ces situations délicates sont devenues beaucoup plus simples une fois que j’ai ajouté plus d’outils à ma boîte à outils: un maillet en caoutchouc, un marteau à billes, un levier, et des coups de poing. Cependant , mon ancien marteau à griffes voit encore sa part d’utilisation.
Je ne pense pas que l’un soit vraiment «meilleur» que l’autre, mais chacun correspond mieux à différents problèmes. En résumé, le modèle simple et la nature orientée vers la scope de la synchronized
me protègent des bogues de mon code, mais ces mêmes avantages sont parfois des obstacles dans des scénarios plus complexes. Ce sont ces scénarios plus complexes que le package simultané a été créé pour aider à résoudre le problème. Mais utiliser ces constructions de plus haut niveau nécessite une gestion plus explicite et prudente du code.
===
Je pense que le JavaDoc décrit bien la distinction entre Lock
et synchronized
(l’accent est mis sur moi):
Les implémentations de verrou fournissent des opérations de locking plus étendues que celles pouvant être obtenues à l’aide de méthodes et d’instructions synchronisées. Ils permettent une structuration plus souple , peuvent avoir des propriétés très différentes et peuvent prendre en charge plusieurs objects Condition associés .
…
L’utilisation de méthodes ou d’énoncés synchronisés donne access au verrou de moniteur implicite associé à chaque object, mais force tous les processus d’acquisition et de libération de verrous à se structurer en blocs : lorsque plusieurs verrous sont acquis, ils doivent être libérés dans l’ordre inverse . tous les verrous doivent être libérés dans le même domaine lexical dans lequel ils ont été acquis .
Bien que le mécanisme de cadrage pour les méthodes et les instructions synchronisées facilite la programmation avec les verrous de contrôle et évite de nombreuses erreurs de programmation courantes impliquant des verrous, il est parfois nécessaire de travailler avec des verrous de manière plus flexible. Par exemple, * * certains algorithmes * pour traverser des structures de données accédées simultanément requièrent l’utilisation du “hand-over” ou “chain lock “ : vous acquérez le verrou du noeud A, puis le noeud B, puis relâchez A et acquérez C, relâchez ensuite B et acquérez D et ainsi de suite. Les implémentations de l’ interface Lock permettent d’utiliser de telles techniques en permettant l’acquisition et la libération d’un verrou dans différentes scopes et en permettant l’acquisition et la libération de plusieurs verrous dans n’importe quel ordre .
Avec cette flexibilité accrue vient une responsabilité supplémentaire . L’ absence de locking structuré en blocs supprime la libération automatique des verrous avec les méthodes et les instructions synchronisées. Dans la plupart des cas, l’idiome suivant doit être utilisé:
…
Lorsque le locking et le délocking se produisent dans différentes étendues , il faut veiller à ce que tout le code exécuté pendant la durée du verrou soit protégé par try-finally ou try-catch pour garantir la libération du verrou si nécessaire.
Les implémentations de verrou offrent des fonctionnalités supplémentaires par rapport à l’utilisation de méthodes et d’instructions synchronisées en fournissant une tentative non bloquante d’acquérir un verrou (tryLock ()), une tentative d’ acquérir le verrou pouvant être interrompu (lockInterrupt lecteur ()) et une tentative d’ acquisition le verrou qui peut expirer (tryLock (long, TimeUnit)).
…
Vous pouvez obtenir tout ce que font les utilitaires de java.util.concurrent avec les primitives de bas niveau comme synchronized
, volatile
ou wait / notify
Cependant, la concurrence est délicate et la plupart des gens ont au moins quelques erreurs, rendant leur code incorrect ou inefficace (ou les deux).
L’API simultanée fournit une approche de niveau supérieur, qui est plus facile à utiliser (et donc plus sûre). En résumé, vous ne devriez plus avoir besoin d’utiliser directement synchronized, volatile, wait, notify
.
La classe Lock elle-même se trouve du côté inférieur de cette boîte à outils, vous n’avez peut-être même pas besoin de l’utiliser directement (vous pouvez utiliser la plupart du temps des Queues
et des sémaphores , etc.).
Il y a 4 facteurs principaux expliquant pourquoi vous souhaitez utiliser synchronized
ou java.util.concurrent.Lock
.
Remarque: le locking synchronisé est ce que je veux dire lorsque je parle de locking insortingnsèque.
Lorsque Java 5 est sorti avec ReentrantLocks, il s’est avéré avoir une différence de débit assez nette, puis un locking insortingnsèque. Si vous recherchez un mécanisme de locking plus rapide et que vous exécutez 1.5, envisagez jucReentrantLock. Le locking insortingnsèque de Java 6 est désormais comparable.
jucLock a différents mécanismes de locking. Lock interruptable – verrouiller jusqu’à ce que le thread de locking soit interrompu; locking temporisé – essayez de verrouiller pendant un certain temps et abandonnez si vous ne réussissez pas; tryLock – essayez de verrouiller si un autre thread maintient le verrou. Tout cela est inclus à part le simple verrou. Le locking insortingnsèque n’offre qu’un locking simple
La principale différence est l’équité, c’est-à-dire les demandes sont-elles traitées FIFO ou peut-il y avoir des échanges? La synchronisation au niveau de la méthode assure une allocation équitable ou FIFO du verrou. En utilisant
synchronized(foo) { }
ou
lock.acquire(); .....lock.release();
n’assure pas l’équité.
Si vous avez beaucoup de querelles pour le verrou, vous pouvez facilement rencontrer des nouvelles où les nouvelles requêtes obtiennent le verrou et les anciennes requêtes sont bloquées. J’ai vu des cas où 200 threads arrivent rapidement pour un verrou et le 2ème à arriver ont été traités en dernier. C’est correct pour certaines applications mais pour d’autres, c’est mortel.
Voir le livre de Brian Goetz intitulé “Concurrence en pratique dans Java”, section 13.3 pour une discussion complète sur ce sujet.
Le livre “Brian Concurrence In Practice” de Brian Goetz, section 13.3: “… Comme le ReentrantLock par défaut, le locking insortingnsèque n’offre aucune garantie d’équité déterministe, mais les garanties d’équité statistique de la plupart des implémentations de locking sont suffisantes …”
Je voudrais append d’autres choses en plus de la réponse de Bert F.
Locks
prennent en charge diverses méthodes pour un contrôle plus précis des verrous, plus expressifs que les moniteurs implicites (verrous synchronized
).
Un verrou fournit un access exclusif à une ressource partagée: un seul thread à la fois peut acquérir le verrou et tout access à la ressource partagée nécessite l’acquisition du verrou en premier. Cependant, certains verrous peuvent autoriser un access simultané à une ressource partagée, telle que le verrou de lecture d’un ReadWriteLock.
Avantages de la synchronisation sur la page de documentation
L’utilisation de méthodes ou d’instructions synchronisées donne access au verrou de moniteur implicite associé à chaque object, mais force toute acquisition et libération de verrou à se produire de manière structurée en blocs
Les implémentations de verrou offrent des fonctionnalités supplémentaires par rapport à l’utilisation de méthodes et d’instructions synchronisées en fournissant une tentative non bloquante d’acquérir un lock (tryLock())
, une tentative d’acquérir le verrou pouvant être interrompu ( lockInterruptibly()
et une tentative d’acquisition le verrou qui peut timeout (tryLock(long, TimeUnit))
.
Une classe Lock peut également fournir un comportement et une sémantique très différents de ceux du locking de moniteur implicite, tels que l’ ordre garanti, l’utilisation non réentrante ou la détection de blocage
ReentrantLock : En termes simples, d’après ma compréhension, ReentrantLock
permet à un object de ré-entrer d’une section critique à une autre section critique. Comme vous avez déjà verrouillé pour entrer dans une section critique, vous pouvez utiliser une autre section critique sur le même object en utilisant le locking actuel.
ReentrantLock
clés de ReentrantLock
conformément à cet article
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.
En dehors de ces trois ReentrantLocks, Java 8 fournit un autre verrou
StampedLock:
Java 8 est livré avec un nouveau type de verrou appelé StampedLock, qui prend également en charge les verrous en lecture et en écriture, comme dans l’exemple ci-dessus. Contrairement à ReadWriteLock, les méthodes de locking d’un StampedLock renvoient un timbre représenté par une valeur longue.
Vous pouvez utiliser ces tampons pour libérer un verrou ou pour vérifier si le verrou est toujours valide. De plus, les verrous estampés prennent en charge un autre mode de locking appelé locking optimiste.
Consultez cet article sur l’utilisation de différents types de ReentrantLock
et StampedLock
.
Lock facilite la vie des programmeurs. Voici quelques situations plus faciles à réaliser avec le locking.
While, le verrou et les conditions sont générés sur le synchronisé. Vous pouvez donc atteindre le même objective avec cela. Cependant, cela pourrait vous rendre la vie difficile et vous empêcher de résoudre le problème.
La principale différence entre le locking et la synchronisation est la suivante: avec les verrous, vous pouvez libérer et acquérir les verrous dans n’importe quel ordre. – avec synchronized, vous ne pouvez libérer les verrous que dans l’ordre dans lequel ils ont été acquis.
Verrouiller et synchroniser un bloc a la même fonction, mais cela dépend de l’utilisation. Considérez la partie ci-dessous
void randomFunction(){ . . . synchronize(this){ //do some functionality } . . . synchronize(this) { // do some functionality } } // end of randomFunction
Dans le cas ci-dessus, si un thread entre dans le bloc de synchronisation, l’autre bloc est également verrouillé. S’il existe plusieurs tels blocs de synchronisation sur le même object, tous les blocs sont verrouillés. Dans de telles situations, java.util.concurrent.Lock peut être utilisé pour empêcher le locking indésirable des blocs