J'ai un peu de difficulté à comprendre les variables volatiles en Java.
J'ai une classe paramétrée contenant une variable volatilité comme: p> Je dois mettre en œuvre certaines opérations de base contre Ces opérations doivent-elles être synchronisées? Puis-je vous échairer avec la méthode suivante? P> lastvalue code> y compris -Value-if-pas-null. P>
public void doSomething() {
String someString;
...
synchronized(this) {
if (lastValue != null) {
someString += lastValue.toString();
}
}
}
3 Réponses :
donc oui, Vous pouvez ajouter une synchronisation (mais vous devrez synchroniser tous les accès en écriture à la variable) ou pour ce cas d'utilisation simple, vous pouvez utiliser une variable locale: p> volatile code> garantit la visibilité (les modifications apportées par un thread seront vues par d'autres threads) mais cela ne garantit pas l'atomicité de plusieurs opérations.
lastvalue code> pourrait devenir
null code> entre
si (lastvalue! = null) code> et
suls + = lastvalue.tostring (); code> et votre code pourrait lancer un
Nullpoinpoincerexception code>. P>
Pourquoi ajouterait une synchronisation à la vérification NULL nécessite l'ajout de la synchronisation aux opérations atomiques par rapport à la variable volatile?
Est-ce que je comprends bien qu'il n'y a pas de notion de "avant" et "après" pour les fils sauf lorsqu'ils sont synchronisés à un moment donné? Les sémantiques de fil de fil permettent un thread de voir une valeur variable ancienne après l'autre fil de fil?
@Roddyofthefrozenpeas Vous avez raison, il vous suffit de synchroniser toutes les opérations d'écriture. Cela est nécessaire car synchronisé code> est utilisé ici pour une exclusion mutuelle, c'est-à-dire en veillant à ce qu'aucun thread ne modifie votre variable entre ces deux lignes. Cela ne peut fonctionner que si tous les threads essayant d'écrire à la variable doivent acquérir cette serrure.
@kutschkem: Cela pourrait arriver parce qu'ils peuvent être mis en cache par le fil de lecture dans son cache de fil-local, c'est la raison de l'utilisation volatile, si l'accès à la variable n'est pas dans un bloc synchronisé.
@KUTSCHKEM OUI, sans synchronisation correcte, si thread a écrit v = 1; code> et thread b par la suite
imprimer (v) code>, vous n'êtes pas garanti qu'il verra quel fil A a fait et cela pourrait ne pas imprimer 1.
@kutschkem exactement - Le Java Le modèle de mémoire définit un "arrive-avant" relation et tout accès qui ne dispose pas de cette relation pourrait se produire conceptuellement dans aucun ordre, même des "impossibles". (Il y a un bon exemple dans la boîte pourpre au début de la section 17.4)
Est-il sécuritaire de faire t lastvaluecopy = lastvalue code>? Cela ne vient pas d'affecter la variable
lastvaluecopy code> à pointer à la même instance de
t code> comme
lastvalue code>? Il semble que la bonne chose à faire serait
T lastvaluecopy = lastvalue.clone () code> ou une méthode d'approvisionnement équivalente de sorte que
lastvaluecopy code> détient une copie de la valeur I> de dernier envalage plutôt que simplement une référence à cela.
@Roddyofthefrozenpeas Cela dépend de ce que T est et de ce que vous essayez d'atteindre. Si vous voulez simplement éviter une NPE, alors mon approche va bien. Si le T est un objet mutable et que vous souhaitez faire un contrôle - ACT-ACT basé sur son état (disons que vous voulez faire quelque chose comme: si (mutableinteger.value ()! = 0) résultat = 10 / mutableInteger .Value (); code> alors mon approche ne sera pas assez bon car l'objet sous-jacent pourrait être muté d'une valeur non 0 à 0 entre les deux lignes, même si vous utilisez une copie de la référence partagée. En ce que cas, vous avez besoin d'un clone profondément.
Cela dépend fortement des propriétés du type T. Dans le cas le plus simple, T est un type immuable, c'est-à-dire quelle que soit la valeur de votre lecture, elle ne changera pas l'état interne. Dans ce cas, vous n'avez pas besoin de synchronisation, il suffit de lire la référence dans une variable locale et de travailler avec elle aussi longtemps que vous en avez besoin. P>
Si T est un type mutable, vous avez besoin de protection contre l'instance qui change son état pendant que vous travaillez avec elle. Dans ce cas, vous devez avoir une synchronisation qui garantit spécifiquement que l'instance de T est protégée par elle. Synchroniser sur Ce em> ne s'assure généralement pas que cela ne vous empêche pas de modifier l'état de T d'un autre endroit. Le verrouillage approprié pour un T spécifique doit être défini par des règles (et il n'y a pas d'élément linguistique en veillant à ne pas casser ces règles). P>
Dans le cas spécial T est mutable et vous avez défini votre ce em> pour être le verrouillage approprié, vous avez besoin de synchronisation - mais vous n'avez plus besoin de volatilité. P>
qui a déclaré que l'utilisation de la volatilité en conjonction avec synchronisée semble suspecte. p>
@Loki Pas nécessairement, mais quand il devient visible, ce sera dans un état cohérent. Si l'objet n'est pas immuable, lorsqu'il devient visible, le thread peut voir l'objet avec certains champs correctement construits et certains champs ayant leur valeur par défaut (FALSE, 0, NULL).
Le traitement parallèle ne garantit pas que les données mettent à jour les références à leur origine. Dans ce cas, vous devez faire une synchronisation explicite sur le fonctionnement utilisé comme objet de référence. Utilisez synchronisé, attendez et notifiez-y. P>
Plus de détails sur: http: // oracle2java. blogspot.com.br/2013/12/java-sincronizar-referencia-entre.html P>
Réécriture
sulsing + = lastvalue.tostrin (); code> comme
suls + = ("" + lastvalue); code> permet de vous débarrasser de la vérification code> null tout à fait.
@dasblinkenlight, vrai, mais seulement si vous êtes ok avec le mot "NULL" montrant dans votre chaîne résultante lorsque le dernier envalage est NULL.
@Ianmlaird Ah, tu as raison, j'ai oublié que Java fait ça (c'est .net qui ne le fait pas).