Il s'agit d'une sorte de sondage sur les problèmes de concurrence courants en Java. Un exemple pourrait être le blocage classique ou la condition de concurrence ou peut-être des bogues de thread EDT dans Swing. Je m'intéresse à la fois à un large éventail de problèmes possibles, mais également aux problèmes les plus courants. Donc, veuillez laisser une réponse spécifique à un bogue de concurrence Java par commentaire et votez si vous en voyez une.
java
multithreading
concurrency
Alex Miller
la source
la source
Réponses:
Le problème de simultanéité le plus courant que j'ai vu n'est pas de réaliser qu'un champ écrit par un thread n'est pas garanti d'être vu par un thread différent. Une application courante de ceci:
Tant que l'arrêt n'est pas volatile ou
setStop
etrun
n'est pas synchronisé, cela n'est pas garanti de fonctionner. Cette erreur est particulièrement diabolique, car dans 99,999%, cela n'aura pas d'importance en pratique car le fil du lecteur verra éventuellement le changement - mais nous ne savons pas combien de temps il l'a vu.la source
Mon problème de concurrence le plus douloureux n ° 1 est survenu lorsque deux bibliothèques open source différentes ont fait quelque chose comme ceci:
À première vue, cela ressemble à un exemple de synchronisation assez trivial. Toutefois; parce que les chaînes sont internées en Java, la chaîne littérale
"LOCK"
s'avère être la même instance dejava.lang.String
(même si elles sont déclarées de manière totalement disparate les unes des autres.) Le résultat est évidemment mauvais.la source
Un problème classique consiste à modifier l'objet sur lequel vous synchronisez lors de la synchronisation:
D'autres threads simultanés se synchronisent alors sur un objet différent et ce bloc ne fournit pas l'exclusion mutuelle attendue.
la source
Un problème courant est l'utilisation de classes comme Calendar et SimpleDateFormat à partir de plusieurs threads (souvent en les mettant en cache dans une variable statique) sans synchronisation. Ces classes ne sont pas sûres pour les threads, donc l'accès multi-thread causera finalement des problèmes étranges avec un état incohérent.
la source
Ne se synchronise pas correctement sur les objets renvoyés par
Collections.synchronizedXXX()
, en particulier pendant l'itération ou plusieurs opérations:C'est faux . Malgré les opérations uniques
synchronized
, l'état de la carte entre les appelscontains
etput
peut être modifié par un autre thread. Ça devrait être:Ou avec une
ConcurrentMap
implémentation:la source
Verrouillage vérifié. Dans l'ensemble.
Le paradigme, dont j'ai commencé à apprendre les problèmes lorsque je travaillais au BEA, est que les gens vérifieront un singleton de la manière suivante:
Cela ne fonctionne jamais, car un autre thread peut avoir pénétré dans le bloc synchronisé et s_instance n'est plus nulle. Le changement naturel est donc de le faire:
Cela ne fonctionne pas non plus, car le modèle de mémoire Java ne le prend pas en charge. Vous devez déclarer s_instance comme volatile pour le faire fonctionner, et même dans ce cas, il ne fonctionne que sur Java 5.
Les personnes qui ne sont pas familières avec les subtilités du modèle de mémoire Java gâchent tout le temps .
la source
Bien que probablement pas exactement ce que vous demandez, le problème le plus fréquent lié à la concurrence que j'ai rencontré (probablement parce qu'il survient dans un code à un seul thread normal) est un
java.util.ConcurrentModificationException
causé par des choses comme:
la source
Il peut être facile de penser que les collections synchronisées vous accordent plus de protection qu'elles ne le font réellement et d'oublier de maintenir le verrou entre les appels. J'ai vu cette erreur à quelques reprises:
Par exemple, dans la deuxième ligne ci-dessus, les méthodes
toArray()
etsize()
sont toutes deux thread-safe à part entière, mais lesize()
est évalué séparément detoArray()
, et le verrou de la liste n'est pas maintenu entre ces deux appels.Si vous exécutez ce code avec un autre thread en supprimant simultanément des éléments de la liste, tôt ou tard, vous vous retrouverez avec un nouveau
String[]
retourné qui est plus grand que nécessaire pour contenir tous les éléments de la liste et qui a des valeurs nulles dans la queue. Il est facile de penser que, puisque les deux appels de méthode à la liste se produisent dans une seule ligne de code, il s'agit en quelque sorte d'une opération atomique, mais ce n'est pas le cas.la source
Le bogue le plus courant que nous voyons là où je travaille est que les programmeurs effectuent de longues opérations, comme des appels au serveur, sur l'EDT, verrouillant l'interface graphique pendant quelques secondes et rendant l'application insensible.
la source
Oublier de wait () (ou Condition.await ()) dans une boucle, vérifier que la condition d'attente est réellement vraie. Sans cela, vous rencontrez des bogues dus à de faux réveils wait (). L'utilisation canonique doit être:
la source
Un autre bogue courant est la mauvaise gestion des exceptions. Lorsqu'un thread d'arrière-plan lève une exception, si vous ne la gérez pas correctement, vous risquez de ne pas voir du tout la trace de la pile. Ou peut-être que votre tâche d'arrière-plan s'arrête et ne redémarre jamais parce que vous n'avez pas réussi à gérer l'exception.
la source
Jusqu'à ce que je prenne un cours avec Brian Goetz, je n'avais pas réalisé que le non-synchronisé
getter
d'un champ privé muté via un synchronisésetter
n'est jamais garanti pour retourner la valeur mise à jour. Ce n'est que lorsqu'une variable est protégée par un bloc synchronisé sur les deux lectures ET écritures que vous obtiendrez la garantie de la dernière valeur de la variable.la source
Pensant que vous écrivez du code à un seul thread, mais en utilisant des statiques mutables (y compris des singletons). Évidemment, ils seront partagés entre les threads. Cela arrive étonnamment souvent.
la source
Les appels de méthode arbitraires ne doivent pas être effectués à partir de blocs synchronisés.
Dave Ray en a parlé dans sa première réponse et, en fait, j'ai également rencontré une impasse liée à l'invocation de méthodes sur des écouteurs à partir d'une méthode synchronisée. Je pense que la leçon la plus générale est que les appels de méthode ne devraient pas être faits "dans la nature" à partir d'un bloc synchronisé - vous n'avez aucune idée si l'appel sera de longue durée, entraînera un blocage, ou autre.
Dans ce cas, et généralement en général, la solution était de réduire la portée du bloc synchronisé pour simplement protéger une section privée critique du code.
De plus, étant donné que nous accédions maintenant à la collection d'écouteurs en dehors d'un bloc synchronisé, nous l'avons changée en une collection copie sur écriture. Ou nous aurions pu simplement faire une copie défensive de la Collection. Le fait est qu'il existe généralement des alternatives pour accéder en toute sécurité à une collection d'objets inconnus.
la source
Le bogue le plus récent lié à la concurrence que j'ai rencontré était un objet qui, dans son constructeur, créait un ExecutorService, mais lorsque l'objet n'était plus référencé, il n'avait jamais arrêté l'ExecutorService. Ainsi, sur une période de plusieurs semaines, des milliers de threads ont fui, provoquant finalement le crash du système. (Techniquement, il ne s'est pas écrasé, mais il a cessé de fonctionner correctement, tout en continuant à fonctionner.)
Techniquement, je suppose que ce n'est pas un problème de concurrence, mais c'est un problème lié à l'utilisation des bibliothèques java.util.concurrency.
la source
Une synchronisation déséquilibrée, en particulier avec Maps, semble être un problème assez courant. Beaucoup de gens pensent qu'il suffit de synchroniser des mises à une carte (pas une ConcurrentMap, mais disons un HashMap) et de ne pas synchroniser sur des get. Cela peut cependant conduire à une boucle infinie lors du re-hachage.
Le même problème (synchronisation partielle) peut se produire partout où vous avez un état partagé avec des lectures et des écritures.
la source
J'ai rencontré un problème de concurrence avec les servlets, lorsqu'il y a des champs mutables qui seront réglés à chaque demande. Mais il n'y a qu'une seule instance de servlet pour toutes les demandes, donc cela fonctionnait parfaitement dans un environnement à un seul utilisateur, mais lorsque plus d'un utilisateur demandait le servlet, des résultats imprévisibles se produisaient.
la source
Ce n'est pas exactement un bogue, mais le pire des péchés est de fournir une bibliothèque que vous souhaitez utiliser par d'autres personnes, mais de ne pas indiquer quelles classes / méthodes sont thread-safe et lesquelles ne doivent être appelées qu'à partir d'un seul thread, etc.
Davantage de personnes devraient utiliser les annotations de concurrence (par exemple @ThreadSafe, @GuardedBy, etc.) décrites dans le livre de Goetz.
la source
Mon plus gros problème a toujours été les blocages, en particulier causés par les auditeurs qui sont tirés avec un verrou maintenu. Dans ces cas, il est très facile d'obtenir un verrouillage inversé entre deux threads. Dans mon cas, entre une simulation exécutée dans un thread et une visualisation de la simulation exécutée dans le thread UI.
EDIT: Déplacement de la deuxième partie pour séparer la réponse.
la source
Démarrer un thread dans le constructeur d'une classe est problématique. Si la classe est étendue, le thread peut être démarré avant que le constructeur de la sous-classe ne soit exécuté.
la source
Classes mutables dans des structures de données partagées
Lorsque cela se produit, le code est beaucoup plus complexe que cet exemple simplifié. Reproduire, trouver et corriger le bogue est difficile. Peut-être que cela pourrait être évité si nous pouvions marquer certaines classes comme immuables et certaines structures de données comme contenant uniquement des objets immuables.
la source
La synchronisation sur un littéral de chaîne ou une constante définie par un littéral de chaîne est (potentiellement) un problème car le littéral de chaîne est interné et sera partagé par toute autre personne dans la JVM utilisant le même littéral de chaîne. Je sais que ce problème est survenu dans les serveurs d'applications et d'autres scénarios de «conteneur».
Exemple:
Dans ce cas, toute personne utilisant la chaîne «foo» pour verrouiller partage le même verrou.
la source
Je crois qu'à l'avenir, le principal problème avec Java sera le (manque de) garanties de visibilité pour les constructeurs. Par exemple, si vous créez la classe suivante
puis lisez simplement la propriété de MyClass a à partir d'un autre thread, MyClass.a pourrait être 0 ou 1, selon l'implémentation et l'humeur de JavaVM. Aujourd'hui, les chances que «a» soit 1 sont très élevées. Mais sur les futures machines NUMA, cela peut être différent. Beaucoup de gens ne sont pas conscients de cela et pensent qu'ils n'ont pas besoin de se soucier du multi-threading pendant la phase d'initialisation.
la source
L'erreur la plus stupide que je fais fréquemment est d'oublier de synchroniser avant d'appeler notify () ou wait () sur un objet.
la source
Utilisation d'un "nouvel objet ()" local comme mutex.
Cela ne sert à rien.
la source
Un autre problème courant de «concurrence» consiste à utiliser du code synchronisé lorsqu'il n'est pas du tout nécessaire. Par exemple, je vois encore des programmeurs utilisant
StringBuffer
ou mêmejava.util.Vector
(comme variables locales de méthode).la source
Plusieurs objets protégés par un verrou, mais auxquels on accède généralement les uns après les autres. Nous avons rencontré quelques cas où les verrous sont obtenus par un code différent dans des ordres différents, ce qui entraîne un blocage.
la source
Ne pas se rendre compte que le
this
dans une classe interne n'est pas lethis
de la classe externe. Généralement dans une classe interne anonyme qui implémenteRunnable
. Le problème fondamental est que la synchronisation fait partie de toutObject
s, il n'y a effectivement pas de vérification de type statique. J'ai vu cela au moins deux fois sur usenet, et cela apparaît également dans Brian Goetz'z Java Concurrency in Practice.Les fermetures BGGA n'en souffrent pas car il n'y en a pas
this
pour la fermeture (this
référence la classe externe). Si vous utilisez des non-this
objets comme verrous, cela permet de contourner ce problème et d'autres.la source
Utilisation d'un objet global tel qu'une variable statique pour le verrouillage.
Cela conduit à de très mauvaises performances en raison de conflits.
la source
Honesly? Avant l'avènement de
java.util.concurrent
, le problème le plus courant que je rencontrais régulièrement était ce que j'appelle "Thrash-Thrashing": les applications qui utilisent des threads pour la concurrence, mais en génèrent trop et finissent par se débattre.la source