10
votes

Les variables volatiles nécessitent-elles un accès synchronisé?

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> xxx pré>

Je dois mettre en œuvre certaines opérations de base contre lastvalue code> y compris -Value-if-pas-null. P>

Ces opérations doivent-elles être synchronisées? Puis-je vous échairer avec la méthode suivante? P>

public void doSomething() {
    String someString;
    ...

    synchronized(this) {
        if (lastValue != null) {
            someString += lastValue.toString();
        }
    }
}


3 commentaires

Réécriture sulsing + = lastvalue.tostrin (); comme suls + = ("" + lastvalue); permet de vous débarrasser de la vérification 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).


3 Réponses :


14
votes

volatile 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.

donc oui, lastvalue pourrait devenir null entre si (lastvalue! = null) et suls + = lastvalue.tostring (); et votre code pourrait lancer un Nullpoinpoincerexception .

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: xxx


8 commentaires

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é 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; et thread b par la suite imprimer (v) , 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 ? Cela ne vient pas d'affecter la variable lastvaluecopy à pointer à la même instance de t comme lastvalue ? Il semble que la bonne chose à faire serait T lastvaluecopy = lastvalue.clone () ou une méthode d'approvisionnement équivalente de sorte que lastvaluecopy détient une copie de la valeur 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 (); 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.



2
votes

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.

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 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).

Dans le cas spécial T est mutable et vous avez défini votre ce pour être le verrouillage approprié, vous avez besoin de synchronisation - mais vous n'avez plus besoin de volatilité.

qui a déclaré que l'utilisation de la volatilité en conjonction avec synchronisée semble suspecte.


1 commentaires

@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).



0
votes

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.

Plus de détails sur: http: // oracle2java. blogspot.com.br/2013/12/java-sincronizar-referencia-entre.html


0 commentaires