Synchronisation vs locking

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.

  1. 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.

  2. 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

  3. Style. Si à la fois 1 et 2 ne font pas partie des catégories qui vous intéressent, la plupart des gens, y compris moi-même, trouveraient la sémantique de locking insortingnsèque plus facile à lire et moins verbeuse que le locking jucLock.
  4. Conditions multiples Un object que vous verrouillez ne peut être notifié et attendu qu’un seul cas. La méthode newCondition de Lock permet à un seul Lock d’avoir plusieurs raisons d’attendre ou de signaler. Je n’ai pas encore eu besoin de cette fonctionnalité dans la pratique, mais c’est une fonctionnalité intéressante pour ceux qui en ont besoin.

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

  1. 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

  2. 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)) .

  3. 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

  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.

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.

  1. Verrouillez une méthode et relâchez le verrou dans une autre méthode.
  2. Vous avez deux threads travaillant sur deux morceaux de code différents, mais le premier thread dépend du second thread pour terminer un morceau de code avant de continuer (tandis que d’autres threads fonctionnent simultanément). Un verrou partagé peut résoudre ce problème assez facilement.
  3. Implémentation de moniteurs. Par exemple, une simple queue où les méthodes put et get sont exécutées à partir de nombreux threads différents. Cependant, ne voulez-vous pas que les mêmes méthodes soient appliquées les unes sur les autres et que les méthodes put et get ne se chevauchent pas? Dans ce cas, une serrure privée rend la vie très facile.

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