"Java Concurrency dans la pratique" donne l'exemple suivant d'une classe dangereuse qui en raison de la nature du modèle de mémoire Java peut finir par fonctionner pour toujours ou d'imprimer 0.
Le problème que cette classe tente de démontrer est que les variables Voici pas "partagé" entre les threads. Donc, la valeur sur le fil voit peut être différente d'un autre fil car ils ne sont pas volatils ou synchronisés. Également en raison de la réorganisation des déclarations autorisées par le JVM Ready = VRAI peut-être défini avant le numéro = 42. P>
Pour moi, cette classe fonctionne toujours bien avec JVM 1.6. Toute idée sur la manière d'obtenir cette classe pour effectuer le comportement incorrect (i.e. Imprimer 0 ou exécuter pour toujours)? P>
public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { public void run() { while (!ready) Thread.yield(); System.out.println(number); } } public static void main(String[] args) { new ReaderThread().start(); number = 42; ready = true; } }
6 Réponses :
Selon votre système d'exploitation, Thread.Yield () pourrait ou non fonctionner. Thread.yield () ne peut pas vraiment être considéré comme une plate-forme indépendante et ne doit pas être utilisée si vous avez besoin de cette hypothèse. P>
Faire l'exemple de faire ce que vous attendez de faire, je pense que c'est plus une question d'architecture de processeur que toute autre chose ... essayez de l'exécuter sur différentes machines, avec différents systèmes d'exploitation, voir ce que vous pouvez en sortir. < / p>
Cela a peu à voir avec Thread.yield ... c'est sur le modèle de mémoire partagé en Java.
pas sûr à 100% sur cela, mais Ce pourrait être associé: p>
Qu'est-ce que l'on entend par réorganisation? P>
Il existe un certain nombre de cas dans lesquels accès aux variables de programme (champs d'instance d'objet, champs statiques de classe et éléments de tableau) peuvent semblent exécuter dans une commande différente de celle spécifiée par le programme. Le compilateur est libre de prendre des libertés avec la commande de instructions dans le nom de l'optimisation. Les processeurs peuvent exécuter instructions en dehors de l'ordre dans certaines circonstances. Les données peuvent être déplacé entre les registres, les caches de processeur et la mémoire principale dans ordre différent de celui spécifié par le programme. P>
Par exemple, si un fil écrit sur le champ A, puis sur le champ B, et La valeur de B ne dépend pas de la valeur d'un, puis du compilateur est libre de réorganiser ces opérations et le cache est libre de chasser B à mémoire principale avant un. Il y a un certain nombre de sources potentielles de réorganisation, telle que le compilateur, le JIT et le cache. P>
Le compilateur, l'exécution et le matériel sont censés conspirer pour créer l'illusion de sémantique as-Seriale, ce qui signifie que dans un programme à une seule-fileté, le programme ne doit pas être capable d'observer la effets des réorganisations. Cependant,
Les réorganisations peuvent entrer en jeu dans des programmes multithreads incorrectement synchronisés, où un thread est capable d'observer les effets d'autres threads et peut être capable de détecter que les accès variables deviennent visibles par d'autres threads dans un ordre différent de celui exécuté ou spécifié dans le programme fort>. p> blockQuote>
+1 L'impression de 0 est due à la réorganisation. Du livre lui-même: "Novisibilité pourrait imprimer zéro car l'écriture à prêts peut être rendue visible pour le fil de lecteur avant le numéro d'écriture, un phénomène appelé réorganisation"
Le modèle de mémoire Java définit ce qui est nécessaire pour travailler et ce qui n'est pas. La "beauté" du code multi-threadé dangereux est que, dans la plupart des situations (en particulier dans les environnements de développement construites), cela fonctionne habituellement. Ce n'est que lorsque vous arrivez à la production avec une meilleure augmentation de l'ordinateur et de la charge et que le JIT commence vraiment à ce que les bugs commencent à mordre. p>
Le problème que vous avez est que vous n'attendez pas assez longtemps pour que le code soit optimisé et que la valeur soit mise en cache.
Lorsqu'un thread sur un système X86_64 lit une valeur pour la première fois, il obtient un fil Copie sûre. Ses changements seulement plus tard, cela peut ne pas voir. Cela peut ne pas être le cas sur d'autres processeurs. P>
Si vous essayez ceci, vous pouvez voir que chaque thread est bloqué avec sa valeur locale. P>
1705 1 % RequiresVolatileMain$MyRunnable::run @ -2 (129 bytes) made not entrant 1705 2 % RequiresVolatileMain$MyRunnable::run @ 4 (129 bytes)
Pourriez-vous fournir un exemple avec une boucle qui démontrera le problème?
J'ai ajouté une explication. Faites-moi savoir si vous avez plus de doutes / questions.
Je ne vois pas comment les impressions démontrent cela. Comment savez-vous que les threads voient des valeurs différentes lorsque vous imprimez un instantané toutes les 100000000 itérations? Aussi, pourquoi la valeur retournerait constamment ..... si le thread 1 exécute la boucle plusieurs fois sans fil 2 en cours d'exécution de la valeur ne se retournera pas. Cela semble assez dépendant de la planification.
Il doit exécuter un milliard de fois sans retourner la valeur. Vous ne pouvez pas que les deux threads ne soient pas systématiquement bouclés et en vérifiant un milliard de fois lorsque vous ne le pensez pas non plus besoin de retourner la valeur (même si votre boîte est sérieusement chargée) à moins que chaque thread agisse comme si la valeur est différente.
Je pense que le point principal à ce sujet est qu'il n'est pas garanti que toutes les JVM réallivent les instructions de la même manière. Il est utilisé comme exemple que différentes réorganisations possibles existent, et donc pour certaines implémentations de la JVM, vous pourriez obtenir des résultats différents. Il arrive que votre JVM soit réorganisant de la même manière à chaque fois, mais cela pourrait ne pas être le cas pour un autre. Le seul moyen de garantir la commande est d'utiliser une synchronisation correcte. P>
Voir le code ci-dessous, il introduit le bogue de visibilité de données sur X86.
Essayé avec jdk8 et jdk7 Trick ne doit pas s'attendre à ce que le processeur fasse la réorganisation plutôt de faire
compilateur pour faire une certaine optimisation qui introduit le bogue de visibilité. P>
blockQuote> Si vous exécutez le code ci-dessus, vous le verrez se bloquer indéfiniment car il ne voit jamais la valeur mise à jour partagéevariable. p> Pour corriger le code déclare le SharedVariable comme volatile. P> < p> Pourquoi la variable normale n'a pas fonctionné et le programme ci-dessus est suspendu? P> f p> partagé sur github: https://github.com/lazysun/concurrency/bloc/com/snippets/src/com/snippets/sharedvariable.java P > p>
Réponse courte no. Mais avez-vous vraiment besoin de vérifier si le saut du toit est dangereux après que quelqu'un vous ait dit et a donné de bonnes raisons?
@Voo ... quoi sur Terre parlez-vous?
@Jbnizet ... Est-ce que quelqu'un utilise-t-il un ordinateur sans plusieurs cœurs ces jours-ci ??
J'aime cette question et découvrez pourquoi votre programme se comporte comme il serait intéressant.
@Jbnizet - Non, du moins pas pour le boîtier des courses. Celui-ci n'est possible que sur une machine à noyau unique (si le
ReaderThread code> devient actif avant que les affectations ne soient effectuées, puis le planificateur ignore le rendement
() code> appels). Sur une machine multi-noyau, il n'y a aucun moyen pour
ReaderThread code> pour bloquer définitivement l'exécution du thread principal et, par conséquent, l'erreur des courses ne se produira jamais.
@Aroth: Le problème n'est pas une faim de fil. C'est la visibilité de la valeur prête. Si vous avez deux cœurs, les deux peuvent cacher leur valeur de prêt et le thread principal la définira à vrai, mais le fil de lecteur ne le verra pas en raison de l'absence de volatil. Un planificateur laissera toujours la chance à un thread d'exécuter ses instructions.
@DD Vous pouvez définir l'affinité sur 1 noyau (cela peut accélérer certaines applications de mémoire)