web-dev-qa-db-fra.com

Dans les sections critiques Java, sur quoi dois-je synchroniser?

En Java, le moyen idiomatique de déclarer des sections critiques dans le code est le suivant:

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

Presque tous les blocs se synchronisent sur this, mais y a-t-il une raison particulière à cela? Y a-t-il d'autres possibilités? Existe-t-il des bonnes pratiques sur quel objet sur lequel se synchroniser? (comme des instances privées de Object?)

67
lindelof

Tout d’abord, notez que les extraits de code suivants sont identiques.

public void foo() {
    synchronized (this) {
        // do something thread-safe
    }
}

et:

public synchronized void foo() {
    // do something thread-safe
}

faire exactement la même chose. Aucune préférence pour l'un d'eux, sauf pour la lisibilité du code et le style.

Lorsque vous synchronisez des méthodes ou des blocs de code, il est important de savoir pourquoi vous faites une telle chose, et quel objet vous verrouillez exactement, et pour quel but.

Notez également qu'il existe des situations dans lesquelles vous souhaiterez synchroniser côté client _ des blocs de code dans lesquels le moniteur que vous demandez (c'est-à-dire l'objet synchronisé) n'est pas nécessairement this, comme dans cet exemple:

Vector v = getSomeGlobalVector();
synchronized (v) {
    // some thread-safe operation on the vector
}

Je vous suggère d’approfondir vos connaissances sur la programmation concurrente, elle vous sera très utile une fois que vous saurez exactement ce qui se passe dans les coulisses. Vous devriez consulter Programmation simultanée en Java , un excellent livre sur le sujet. Si vous souhaitez vous plonger rapidement dans le sujet, consultez Java Concurrency @ Sun

46
Yuval Adam

Comme les répondants précédents l'ont noté, il est préférable de synchroniser sur un objet de portée limitée (en d'autres termes, choisissez la portée la plus restrictive possible et utilisez-la.) En particulier, la synchronisation sur this est une mauvaise idée, sauf si vous avez l'intention d'autoriser les utilisateurs de votre classe à obtenir le verrou.

Un cas particulièrement déplorable se présente toutefois si vous choisissez de synchroniser sur un Java.lang.String. Les chaînes peuvent être (et sont presque toujours en pratique) internées. Cela signifie que chaque chaîne de contenu égal - dans ENTIRE JVM - s'avère être la même chaîne dans les coulisses. Cela signifie que si vous synchronisez sur une chaîne, une autre section de code (complètement disparate) qui verrouille également une chaîne avec le même contenu verrouille également votre code.

Une fois, j'étais en train de résoudre un blocage dans un système de production et de suivre (très douloureusement) le blocage dans deux packages Open Source complètement disparates synchronisés chacun sur une instance de String dont le contenu était à la fois "LOCK".

60
Jared

J'essaie d'éviter la synchronisation sur this car cela permettrait à toutes les personnes de l'extérieur possédant une référence à cet objet de bloquer ma synchronisation. Au lieu de cela, je crée un objet de synchronisation local:

public class Foo {
    private final Object syncObject = new Object();
    …
}

Maintenant, je peux utiliser cet objet pour la synchronisation sans craindre que quiconque «vole» le verrou.

37
Bombe

Juste pour souligner le fait qu'il existe également des ReadWriteLocks disponibles en Java, trouvés sous le nom Java.util.concurrent.locks.ReadWriteLock.

Dans la plupart de mes utilisations, je sépare mes verrouillages en "pour lire" et "pour les mises à jour". Si vous utilisez simplement un mot-clé synchronisé, toutes les lectures dans la même méthode/le même bloc de code seront «mises en file d'attente». Un seul thread peut accéder au bloc à la fois. 

Dans la plupart des cas, vous n'avez jamais à vous soucier des problèmes de concurrence si vous ne faites que lire. C'est lorsque vous écrivez que vous vous inquiétez des mises à jour simultanées (entraînant une perte de données) ou de la lecture lors d'une écriture (mises à jour partielles), qui vous préoccupent.

Par conséquent, un verrou en lecture/écriture me semble plus logique lors de la programmation multithread.

6
Kent Lai

Vous aurez envie de synchroniser sur un objet qui peut servir de mutex. Si l'instance actuelle (le this référence) convient (pas un Singleton, par exemple), vous pouvez l'utiliser, comme en Java, tout Object peut servir de Mutex.

Dans d'autres cas, vous souhaiterez peut-être partager un mutex entre plusieurs classes, si certaines de ces classes peuvent avoir besoin d'accéder aux mêmes ressources.

Cela dépend beaucoup de l'environnement dans lequel vous travaillez et du type de système que vous construisez. Dans la plupart des applications Java EE que j'ai vues, il n'y a en réalité aucun besoin réel de synchronisation ...

4
Electric Monk

Personnellement, je pense que les réponses qui insistent sur le fait qu'il n'est jamais ou rarement correct de synchroniser sur this sont erronées. Je pense que cela dépend de votre API. Si votre classe est une implémentation threadsafe et que vous la documentez, vous devriez alors utiliser this. Si la synchronisation ne consiste pas à rendre chaque instance de la classe comme un threadsafe complet lors de l'appel de ses méthodes publiques, vous devez utiliser un objet interne privé. Les composants de bibliothèque réutilisables souvent entrent dans la catégorie précédente - vous devez bien réfléchir avant d'empêcher l'utilisateur d'envelopper votre API dans une synchronisation externe.

Dans le premier cas, utiliser this permet à plusieurs méthodes d'être appelées de manière atomique. PrintWriter en est un exemple. Vous pouvez générer plusieurs lignes (par exemple, une trace de pile dans la console/le consignateur) et garantir qu’elles apparaissent ensemble. Dans ce cas, le fait de masquer l’objet de synchronisation en interne est une véritable gêne. Les wrappers de collection synchronisés sont un autre exemple. Dans ce cas, vous devez synchroniser sur l'objet de collection lui-même pour pouvoir effectuer une itération. étant donné que l'itération consiste en plusieurs invocations de méthode, vous ne pouvez pas le protéger totalement en interne.

Dans ce dernier cas, j'utilise un objet simple:

private Object mutex=new Object();

Cependant, après avoir vu de nombreuses décharges de machine virtuelle Java et traces de pile indiquant qu'un verrou est "une instance de Java.lang.Object ()", je dois dire que l'utilisation d'une classe interne peut souvent être plus utile, comme d'autres l'ont suggéré.

Quoi qu'il en soit, cela vaut mes deux bits.

Edit: Une autre chose, lors de la synchronisation sur this, je préfère synchroniser les méthodes et garder les méthodes très granulaires. Je pense que c'est plus clair et plus concis.

4
Lawrence Dol

La synchronisation en Java implique souvent la synchronisation des opérations sur la même instance. La synchronisation sur this est alors très idiomatique puisque this est une référence partagée automatiquement disponible entre différentes méthodes d'instance (ou sections de) d'une classe.

Utiliser une autre référence spécifiquement pour le verrouillage, en déclarant et en initialisant un champ privé Object lock = new Object() par exemple, est quelque chose que je n'ai jamais eu besoin ni utilisé. Je pense que ce n'est utile que lorsque vous avez besoin d'une synchronisation externe sur au moins deux ressources non synchronisées à l'intérieur d'un objet, bien que j'essaie toujours de reformuler une telle situation dans un format plus simple.

Quoi qu'il en soit, implicitement (méthode synchronisée) ou explicite synchronized(this) est beaucoup utilisé, également dans les bibliothèques Java. C'est un bon idiome et, le cas échéant, devrait toujours être votre premier choix.

2
eljenso

La synchronisation dépend de ce que les autres threads susceptibles d'entrer en conflit avec cet appel de méthode peuvent synchroniser. 

Si this est un objet utilisé par un seul thread et que nous accédons à un objet mutable qui est partagé entre les threads, un bon candidat consiste à synchroniser sur cet objet - la synchronisation sur this n'a pas de point puisqu'un autre thread modifiant cet objet partagé Je ne sais même pas this, mais je connais cet objet.

Par contre, la synchronisation sur this a du sens si plusieurs threads appellent les méthodes de cet objet en même temps, par exemple si nous sommes dans un singleton.

Notez qu'une méthode syncronisée n'est souvent pas la meilleure option, car nous gardons un verrou tout au long de son exécution. Si elle contient des parties consommant beaucoup de temps mais ne prenant pas le temps, et une partie ne prenant pas autant de temps avec une thread, la synchronisation sur la méthode est très fausse.

1
Hans-Peter Störr

la meilleure pratique est de créer un objet uniquement pour fournir le verrou:

private final Object lock = new Object();

private void doSomething() {
  // thread-safe code
  synchronized(lock) {
    // thread-unsafe code
  }
  // thread-safe code
}

En faisant cela, vous êtes sûr qu'aucun code d'appel ne peut jamais bloquer votre méthode par une ligne synchronized(yourObject) non intentionnelle.

( Crédits à @jared et @ yuval-adam qui l'ont expliqué plus en détail ci-dessus. )

Mon hypothèse est que la popularité d'utiliser this dans les tutoriels est venue des débuts de Sun javadoc: https://docs.Oracle.com/javase/tutorial/essential/concurrency/locksync.html

0
epox

Presque tous les blocs se synchronisent là-dessus, mais y a-t-il une raison particulière à cela? Y a-t-il d'autres possibilités?

Cette déclaration synchronise la méthode entière.

private synchronized void doSomething() {

Cette déclaration synchronisait une partie du bloc de code au lieu de la méthode entière.

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

De la documentation Oracle page

rendre ces méthodes synchronisées a deux effets:

Premièrement, il n'est pas possible que deux appels de méthodes synchronisées sur le même objet s'entrelacent. Lorsqu'un thread exécute une méthode synchronisée pour un objet, tous les autres threads appelant des méthodes synchronisées pour le même bloc d'objet (suspendre l'exécution) jusqu'à ce que le premier thread soit terminé avec l'objet.

Y a-t-il d'autres possibilités? Existe-t-il des bonnes pratiques sur quel objet sur lequel se synchroniser? (comme des instances privées de Object?)

Il existe de nombreuses possibilités et alternatives à la synchronisation. Vous pouvez sécuriser votre thread de code en utilisant une concurrence de haut niveau APIs _ (disponible depuis la version JDK 1.5)

Lock objects
Executors
Concurrent collections
Atomic variables
ThreadLocalRandom

Reportez-vous aux questions SE ci-dessous pour plus de détails:

Synchronization vs Lock

_ { Eviter la synchronisation (celle-ci) en Java?

0
Ravindra babu