Dans quelles situations pourraient un bloc synchronisé vide atteindre la sémantique de filetage correct?
-
22-08-2019 - |
Question
Je regardais à travers un rapport de Findbugs sur ma base de code et l'un des motifs qui a été déclenché était pour un bloc de synchronzied
vide (ie synchronized (var) {}
). Le :
vide blocs synchronisés sont beaucoup plus subtile et difficile à utiliser correctement que la plupart des gens reconnaissent et vide blocs synchronisés sont presque jamais meilleure solution que moins artificielle solutions.
Dans mon cas, il a eu lieu parce que le contenu du bloc avait été commenté, mais la déclaration de synchronized
était toujours là. Dans quelles situations pourraient un bloc synchronized
vide atteindre la sémantique de filetage correct?
La solution
Un bloc vide synchronisé attendra jusqu'à ce que personne d'autre utilise ce synchroniseur. Cela peut être ce que vous voulez, mais parce que vous n'êtes pas protégé par le code qui suit dans le bloc synchronisé, rien n'empêche quelqu'un d'autre de modifier ce que jamais il était que vous attendiez pendant que vous exécutez le code suivant. C'est presque jamais ce que vous voulez.
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
- 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é).
- 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).
- 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 »).
- (Toute modification aux variables normalement maintenant être écrit à « principale » la mémoire, mais nous avons geti3 () sans aucune modification.)
- Le fil libère le verrou sur le moniteur pour objet ce sujet.
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!