197 votes

Synchronisation vs Verrouillage

java.util.concurrent API fournit une classe appelée Lock, qui permet essentiellement de sérialiser le contrôle afin d'accéder à la ressource critique. Elle propose des méthodes telles que park() et unpark().

Nous pouvons faire des choses similaires si nous utilisons le mot clé synchronized et en utilisant les méthodes wait() et notify() notifyAll().

Je me demande laquelle de ces méthodes est meilleure en pratique et pourquoi?

1 votes

189voto

Steven Schlansker Points 17463

Si vous voulez simplement verrouiller un objet, je préfère utiliser synchronized

Exemple:

Lock.acquire();
doSomethingNifty(); // Lance une NPE!
Lock.release(); // Oh non, nous ne libérons jamais le verrou!

Vous devez explicitement faire try{} finally{} partout.

Alors qu'avec synchronized, c'est super clair et impossible de se tromper:

synchronized(myObject) {
    doSomethingNifty();
}

Cela dit, les Locks peuvent être plus utiles pour des choses plus compliquées où vous ne pouvez pas acquérir et libérer de manière aussi propre. Je préférerais honnêtement éviter d'utiliser des Locks nus en premier lieu, et simplement opter pour un contrôle de la concurrence plus sophistiqué comme un CyclicBarrier ou un LinkedBlockingQueue, s'ils répondent à vos besoins.

Je n'ai jamais eu de raison d'utiliser wait() ou notify() mais il pourrait y en avoir de bonnes.

1 votes

Quelle est la différence entre wait/notify et park/unpark de LockSupport? docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks‌​/…

8 votes

Tout d'abord, l'exemple avait du sens avec des verrous, mais ensuite j'ai réalisé que si vous utilisez un bloc try finally, ce problème serait évité car les verrous ne seraient pas relâchés.

0 votes

Ahh... Un de ces moments pour apprécier le modèle RAII en C++. std::lock_guard

71voto

Bert F Points 27237

Je me demande lequel de ces deux est meilleur en pratique et pourquoi?

J'ai trouvé que Lock et Condition (et d'autres nouvelles classes concurrentes) ne sont que d'autres outils dans la boîte à outils. Je pouvais faire à peu près tout ce dont j'avais besoin avec mon vieux marteau griffe (le mot-clé synchronized), mais c'était gênant à utiliser dans certaines situations. Plusieurs de ces situations gênantes sont devenues beaucoup plus simples une fois que j'ai ajouté plus d'outils à ma boîte à outils : un maillet en caoutchouc, un marteau à panne ronde, un pied-de-biche, et des chasse-clous. Cependant, mon vieux marteau griffe voit encore son lot d'utilisation.

Je ne pense pas qu'un soit vraiment "meilleur" que l'autre, mais plutôt que chacun convient mieux à différents problèmes. En un mot, le modèle simple et la nature orientée vers la portée de synchronized m'aident à me protéger des bugs dans mon code, mais ces mêmes avantages sont parfois des obstacles dans des scénarios plus complexes. Ce sont ces scénarios plus complexes pour lesquels le package concurrent a été créé pour aider à résoudre. Mais l'utilisation de ces constructions de niveau supérieur nécessite une gestion plus explicite et soigneuse dans le code.

\===

Je pense que le JavaDoc fait un bon travail pour décrire la distinction entre Lock et synchronized (l'accent est mis sur moi):

Les implémentations de Lock fournissent des opérations de verrouillage plus étendues que celles obtenues avec des méthodes et des instructions synchronisées. Elles permettent une structuration plus flexible, peuvent avoir des propriétés très différentes, et peuvent prendre en charge plusieurs objets Condition associés.

...

L'utilisation de méthodes synchronisées ou d'instructions donne accès au verrou implicite associé à chaque objet, mais force l'acquisition et la libération des verrous à se produire de manière structurée par bloc : lorsque plusieurs verrous sont acquis ils doivent être libérés dans l'ordre inverse, et tous les verrous doivent être libérés dans la même portée lexicale dans laquelle ils ont été acquis.

Alors que le mécanisme de portée pour les méthodes et instructions synchronisées rend beaucoup plus facile de programmer avec des verrous de moniteur, et aide à éviter de nombreuses erreurs de programmation courantes impliquant des verrous, il y a des occasions où vous devez travailler avec des verrous de manière plus flexible. Par exemple, **certains algorithmes* pour traverser des structures de données accessibles de manière concurrente nécessitent l'utilisation de "verrouillage main dans la main" ou "verrouillage en chaîne" : vous acquérez le verrou du nœud A, puis du nœud B, puis relâchez A et acquérez C, puis relâchez B et acquérez D et ainsi de suite. Les implémentations de l'interface Lock permettent l'utilisation de telles techniques en permettant l'acquisition et la libération d'un verrou dans des portées différentes, et permettant l'acquisition et la libération dans n'importe quel ordre de plusieurs verrous.

Avec cette flexibilité accrue vient une responsabilité supplémentaire. L'absence de verrouillage structuré par bloc supprime la libération automatique des verrous qui se produit avec les méthodes et instructions synchronisées. Dans la plupart des cas, l'idiome suivant devrait être utilisé:

...

Lorsque le verrouillage et le déverrouillage se produisent dans des portées différentes, il faut veiller à s'assurer que tout le code qui est exécuté pendant que le verrou est détenu est protégé par un bloc try-finally ou try-catch pour assurer que le verrou est libéré quand nécessaire.

Les implémentations de Lock fournissent des fonctionnalités supplémentaires par rapport à l'utilisation de méthodes et instructions synchronisées en fournissant une tentative de verrouillage non bloquante (tryLock()), une tentative de verrouillage qui peut être interrompue (lockInterruptibly()), et une tentative de verrouillage qui peut expirer (tryLock(long, TimeUnit)).

...

27voto

Thilo Points 108673

Vous pouvez accomplir tout ce que les utilitaires dans java.util.concurrent font avec les primitives de bas niveau comme synchronized, volatile, ou wait / notify

Cependant, la concurrence est délicate, et la plupart des gens se trompent au moins sur certaines parties, rendant ainsi leur code incorrect ou inefficace (ou les deux).

L'API concurrente fournit une approche de plus haut niveau, qui est plus facile (et donc plus sûre) à utiliser. 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é de bas niveau de cette boîte à outils, vous n'aurez peut-être même pas besoin de l'utiliser directement (vous pouvez utiliser Queues et Semaphore et d'autres, etc, la plupart du temps).

2 votes

Est-ce que le simple attente/notification est considéré comme une primitive de plus bas niveau que le park/unpark de java.util.concurrent.locks.LockSupport, ou est-ce l'inverse?

0 votes

@Pacerier: Je considère les deux comme étant de bas niveau (c'est-à-dire quelque chose que vous voulez éviter d'utiliser directement en tant que programmeur d'application), mais certainement les parties les plus bas niveau de java.util.concurrency (comme le package de verrous) sont construites au-dessus des primitives natives wait/notify de la JVM (qui sont encore plus bas niveau).

2 votes

Non je veux dire parmi les 3 : Thread.sleep/interrupt, Object.wait/notify, LockSupport.park/unpark, lequel est le plus primitif ?

16voto

John Vint Points 19804

Il existe 4 principaux facteurs expliquant pourquoi vous voudriez utiliser synchronized ou java.util.concurrent.Lock.

Remarque : Le verrouillage synchronisé est ce que je veux dire par verrouillage intrinsèque.

  1. Lorsque Java 5 est sorti avec les ReentrantLocks, ils se sont révélés avoir une différence de débit assez remarquable par rapport au verrouillage intrinsèque. Si vous recherchez un mécanisme de verrouillage plus rapide et que vous exécutez la version 1.5, envisagez j.u.c.ReentrantLock. Le verrouillage intrinsèque de Java 6 est maintenant comparable.

  2. j.u.c.Lock possède différents mécanismes de verrouillage. Verrou interruptible - tente de verrouiller jusqu'à ce que le thread de verrouillage soit interrompu ; verrou chronométré - tente de verrouiller pendant une certaine durée et abandonne si vous ne réussissez pas ; tryLock - tente de verrouiller, si un autre thread détient le verrou, abandonnez. Tout cela est inclus en plus du simple verrou. Le verrouillage intrinsèque n'offre que le verrouillage simple.

  3. Style. Si ni 1 ni 2 ne correspondent aux catégories qui vous préoccupent le plus, la plupart des gens, y compris moi-même, trouveraient la sémantique de verrouillage intrinsèque plus facile à lire et moins verbeuse que le verrouillage de j.u.c.Lock.

  4. Conditions multiples. Un objet sur lequel vous bloquez ne peut être notifié et attendre qu'un seul cas. La méthode newCondition de Lock permet à un verrou unique d'avoir plusieurs raisons d'attendre ou de signaler. Je n'ai pas encore eu besoin de cette fonctionnalité en pratique, mais c'est une fonctionnalité intéressante pour ceux qui en ont besoin.

0 votes

J'ai aimé les détails de votre commentaire. Je voudrais ajouter un autre point de puce - le ReadWriteLock fournit un comportement utile si vous travaillez avec plusieurs fils d'exécution, dont seuls certains doivent écrire dans l'objet. Plusieurs fils d'exécution peuvent lire simultanément l'objet et ne sont bloqués que si un autre fil d'exécution est déjà en train d'écrire dessus.

0 votes

Pour ajouter au 4ème point - Dans j.u.c.ArrayBlockingQueue, le verrou a 2 raisons d'attendre : la file non vide et la file non pleine. Pour cette raison, j.u.c.ArrayBlockingQueue utilise un verrou explicite et lock.newCondition ().

3voto

bodrin Points 61

Livre "Java Concurrency In Practice" de Brian Goetz, section 13.3 : "…Comme le ReentrantLock par défaut, le verrouillage intrinsèque n'offre aucune garantie de justice déterministe, mais les garanties de justice statistique de la plupart des implémentations de verrouillage sont suffisamment bonnes pour presque toutes les situations…"

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X