Dans quelles situations pourraient un bloc synchronisé vide atteindre la sémantique de filetage correct?

StackOverflow https://stackoverflow.com/questions/686415

Autres conseils

Les réponses antérieures ne parviennent pas à souligner la chose la plus utile sur les blocs de synchronized vides: ils peuvent assurer la visibilité des changements variables et d'autres actions dans les discussions. Comme jtahlborn indique, la synchronisation impose une « barrière de mémoire » sur le compilateur qui l'oblige à débusquer et rafraîchir ses caches. Mais je ne trouve pas où « snake discute », alors je me suis écrit une réponse.

int variable;

void test() // This code is INCORRECT
{
    new Thread( () ->  // A
    {
        variable = 9;
        for( ;; )
        {
            // Do other stuff
        }
    }).start();

    new Thread( () ->  // B
    {
        for( ;; )
        {
            if( variable == 9 ) System.exit( 0 );
        }
    }).start();
}

Le programme ci-dessus est incorrect. La valeur de la variable peut être mis en cache localement dans le fil A ou B ou les deux. Alors B pourrait ne jamais lire la valeur de 9 que A écrit, et pourrait donc en boucle pour toujours.

Faire un changement visible dans la variable fils en utilisant des blocs de synchronized vides

Une correction possible est d'ajouter un modificateur volatile (efficace "pas de cache") à la variable. Parfois, cela est inefficace, cependant, parce qu'il interdit totalement la mise en cache de la variable. Les blocs vides de synchronized, d'autre part, n'empêchez pas la mise en cache. Tout ce qu'ils font est la force les caches pour synchroniser avec la mémoire principale à certains points critiques. Par exemple: *

int variable;

void test() // Corrected version
{
    new Thread( () ->  // A
    {
        variable = 9;
        synchronized( o ) {} // Flush to main memory
        for( ;; )
        {
            // Do other stuff
        }
    }).start();

    new Thread( () ->  // B
    {
        for( ;; )
        {
            synchronized( o ) {} // Refresh from main memory
            if( variable == 9 ) System.exit( 0 );
        }
    }).start();
}

final Object o = new Object();

Comment la visibilité des garanties du modèle de mémoire

Les deux threads doivent synchroniser sur le même objet afin de garantir la visibilité. Cette garantie repose sur le Java modèle mémoire , en particulier sur la règle selon laquelle un « déverrouiller l'action sur l'écran m synchronise avec toutes les actions de blocage ultérieures sur m » et ainsi se passe-avant les Actions. Ainsi, le déverrouillage d'un moniteur de O à la queue de son bloc de synchronized se produit avant-blocage ultérieur de B à la tête de son bloc. (Note, il est cet étrange ordre tête de la queue de la relation qui explique pourquoi les corps peuvent être vides.) Étant donné que l'écriture de A précède le déverrouillage et le verrouillage de B précède la lecture, la relation doit se prolonger pour couvrir à la fois écriture et de lecture: < em> écriture se passe-t-avant lu . Il est essentiel ce, la relation étendue qui rend le programme révisé correct en termes de modèle de mémoire.

Je pense que c'est l'utilisation la plus importante pour les blocs synchronized vides.


* Je parle comme si elle était une question de la mise en cache du processeur parce que je pense que c'est un moyen utile de le visionner. En vérité, comme Aleksandr Dubinsky a commenté, « tous les processeurs modernes sont cache cohérente. La relation se passe-avant est plus sur ce que le compilateur est autorisé à le faire plutôt que de la CPU.

Il sert à être le cas que la spécification de certaines opérations de barrière de mémoire implicite est produite. Cependant, la spécification a changé et la spécification originale n'a jamais été mis en œuvre correctement. Il peut être utilisé pour attendre un autre thread pour libérer le verrou, mais la coordination que l'autre thread a déjà acquis le verrou serait délicat.

fait un peu Synchroniser peu plus que d'attendre, alors que le codage inélégante cela pourrait obtenir l'effet recherché.

De http://www.javaperformancetuning.com/news/qotm030.shtml

  
      
  1. Le fil acquiert le verrou sur le moniteur pour objet ce (en supposant que le moniteur est déverrouillé, sinon le temps d'attente de fil jusqu'à ce que le moniteur est déverrouillé).
  2.   
  3. La mémoire de fil débusque toutes ses variables, à savoir qu'il a toutes ses variables effectivement lues à partir de « principale » mémoire (JVMs peut utiliser des ensembles sales pour optimiser cette sorte que seules les variables « sales » sont vidées, mais sur le plan conceptuel c'est le même . Voir la section 17.9 de la spécification du langage Java).
  4.   
  5. Le bloc de code est exécuté (dans ce cas, le réglage de la valeur de retour à la valeur actuelle de i3, qui peut viennent d'être rétablies à partir de la mémoire « principale »).
  6.   
  7. (Toute modification aux variables normalement maintenant être écrit à « principale » la mémoire, mais nous avons geti3 () sans aucune modification.)
  8.   
  9. Le fil libère le verrou sur le moniteur pour objet ce sujet.
  10.   

Pour un regard en profondeur dans le modèle de mémoire de Java, un coup d'oeil à cette vidéo de "sujets avancés dans les langages de programmation de Google série: http://www.youtube.com/watch?v=1FX4zco0ziY

Il donne une vue d'ensemble très agréable de ce que le compilateur peut (souvent en théorie, mais parfois dans la pratique) faire à votre code. choses essentielles pour tout programmeur sérieux Java!

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top