Un avertissement s'affiche chaque fois que je synchronise sur un champ de classe non final. Voici le code:
public class X
{
private Object o;
public void setO(Object o)
{
this.o = o;
}
public void x()
{
synchronized (o) // synchronization on a non-final field
{
}
}
}
j'ai donc changé le codage de la manière suivante:
public class X
{
private final Object o;
public X()
{
o = new Object();
}
public void x()
{
synchronized (o)
{
}
}
}
Je ne suis pas sûr que le code ci-dessus soit le bon moyen de synchroniser sur un champ de classe non final. Comment synchroniser un champ non final?
o
auquel il est fait référence au moment où le bloc synchronisé a été atteint. Si l'objet quio
fait référence aux changements, un autre thread peut venir et exécuter le bloc de code synchronisé.this
non plus - je recommanderais de créer une variable finale dans la classe uniquement à des fins de verrouillage , ce qui empêche quiconque de se verrouiller sur le même objet.Ce n'est vraiment pas une bonne idée - car vos blocs synchronisés ne sont plus vraiment synchronisés de manière cohérente.
En supposant que les blocs synchronisés sont destinés à garantir qu'un seul thread accède à certaines données partagées à la fois, considérez:
Pourquoi voudriez-vous que cela se produise? Peut - être il y a des situations très spécialisés où il est logique ... mais vous auriez à me présenter un cas d'utilisation spécifique (ainsi que des moyens d'atténuer le genre de scénario que j'ai donné ci - dessus) avant que je serais heureux avec il.
la source
o
) - et à mi-chemin de son exécution, commencez à muter une liste différente. En quoi serait-ce une bonne idée? Je pense que nous sommes fondamentalement en désaccord sur la question de savoir si c'est une bonne idée de verrouiller les objets que vous touchez d'une autre manière. Je préférerais pouvoir raisonner sur mon code sans aucune connaissance de ce que fait un autre code en termes de verrouillage.Je suis d'accord avec l'un des commentaires de John: Vous devez toujours utiliser un mannequin de verrouillage final lors de l'accès à une variable non finale pour éviter les incohérences en cas de changement de référence de la variable. Donc dans tous les cas et en règle générale:
Règle n ° 1: Si un champ n'est pas final, utilisez toujours un mannequin de verrouillage final (privé).
Raison n ° 1: vous maintenez le verrou et changez vous-même la référence de la variable. Un autre thread en attente en dehors du verrou synchronisé pourra entrer dans le bloc protégé.
Raison n ° 2: vous maintenez le verrou et un autre thread modifie la référence de la variable. Le résultat est le même: un autre thread peut entrer dans le bloc protégé.
Mais lors de l'utilisation d'un factice de verrouillage final, il y a un autre problème : vous pouvez obtenir des données erronées, car votre objet non final ne sera synchronisé avec la RAM que lors de l'appel de synchronize (object). Donc, comme deuxième règle de base:
Règle n ° 2: Lors du verrouillage d'un objet non final, vous devez toujours faire les deux: Utiliser un factice de verrouillage final et le verrou de l'objet non final pour des raisons de synchronisation RAM. (La seule alternative sera de déclarer tous les champs de l'objet comme volatils!)
Ces verrous sont également appelés «verrous imbriqués». Notez que vous devez toujours les appeler dans le même ordre, sinon vous obtiendrez un dead lock :
public class X { private final LOCK; private Object o; public void setO(Object o){ this.o = o; } public void x() { synchronized (LOCK) { synchronized(o){ //do something with o... } } } }
Comme vous pouvez le voir, j'écris les deux verrous directement sur la même ligne, car ils vont toujours ensemble. Comme ça, vous pouvez même faire 10 verrous d'imbrication:
synchronized (LOCK1) { synchronized (LOCK2) { synchronized (LOCK3) { synchronized (LOCK4) { //entering the locked space } } } }
Notez que ce code ne cassera pas si vous acquérez simplement un verrou interne comme
synchronized (LOCK3)
par un autre thread. Mais cela se cassera si vous appelez dans un autre thread quelque chose comme ceci:synchronized (LOCK4) { synchronized (LOCK1) { //dead lock! synchronized (LOCK3) { synchronized (LOCK2) { //will never enter here... } } } }
Il n'existe qu'une seule solution de contournement autour de ces verrous imbriqués lors de la gestion des champs non finaux:
Règle n ° 2 - Alternative: Déclarez tous les champs de l'objet comme volatils. (Je ne parlerai pas ici des inconvénients de faire cela, par exemple empêcher tout stockage dans les caches de niveau x même pour les lectures, etc.)
Donc, aioobe a tout à fait raison: utilisez simplement java.util.concurrent. Ou commencez à tout comprendre sur la synchronisation et faites-le vous-même avec des verrous imbriqués. ;)
Pour plus de détails sur les raisons pour lesquelles la synchronisation sur les champs non finaux est interrompue, jetez un œil à mon cas de test: https://stackoverflow.com/a/21460055/2012947
Et pour plus de détails sur les raisons pour lesquelles vous avez besoin d'être synchronisé en raison de la RAM et des caches, regardez ici: https://stackoverflow.com/a/21409975/2012947
la source
o
avec un synchronisé (LOCK) pour établir une relation «arrive avant» entre le réglage et l'objet de lectureo
. Je discute de cela dans une question similaire à la mienne: stackoverflow.com/questions/32852464/…Je ne vois pas vraiment la bonne réponse ici, c'est-à-dire que c'est parfaitement normal de le faire.
Je ne sais même pas pourquoi c'est un avertissement, il n'y a rien de mal à cela. La JVM s'assure que vous récupérez un objet valide (ou nul) lorsque vous lisez une valeur, et vous pouvez synchroniser sur n'importe quel objet.
Si vous prévoyez de changer réellement le verrou pendant son utilisation (par opposition, par exemple, à le changer à partir d'une méthode init, avant de commencer à l'utiliser), vous devez créer la variable que vous prévoyez de changer
volatile
. Ensuite, tout ce que vous avez à faire est de synchroniser à la fois l'ancien et le nouvel objet, et vous pouvez modifier la valeur en toute sécuritépublic volatile Object lock;
...
synchronized (lock) { synchronized (newObject) { lock = newObject; } }
Là. Ce n'est pas compliqué, écrire du code avec des verrous (mutex) est en fait assez facile. Ecrire du code sans eux (code sans verrouillage) est ce qui est difficile.
la source
EDIT: Donc, cette solution (comme suggéré par Jon Skeet) pourrait avoir un problème avec l'atomicité de l'implémentation de "synchronized (object) {}" pendant que la référence de l'objet change. J'ai demandé séparément et selon M. erickson, ce n'est pas thread-safe - voir: L'entrée de bloc synchronisé est-elle atomique? . Alors prenez-le comme exemple comment NE PAS le faire - avec des liens pourquoi;)
Voir le code comment cela fonctionnerait si synchronized () était atomique:
public class Main { static class Config{ char a='0'; char b='0'; public void log(){ synchronized(this){ System.out.println(""+a+","+b); } } } static Config cfg = new Config(); static class Doer extends Thread { char id; Doer(char id) { this.id = id; } public void mySleep(long ms){ try{Thread.sleep(ms);}catch(Exception ex){ex.printStackTrace();} } public void run() { System.out.println("Doer "+id+" beg"); if(id == 'X'){ synchronized (cfg){ cfg.a=id; mySleep(1000); // do not forget to put synchronize(cfg) over setting new cfg - otherwise following will happend // here it would be modifying different cfg (cos Y will change it). // Another problem would be that new cfg would be in parallel modified by Z cos synchronized is applied on new object cfg.b=id; } } if(id == 'Y'){ mySleep(333); synchronized(cfg) // comment this and you will see inconsistency in log - if you keep it I think all is ok { cfg = new Config(); // introduce new configuration // be aware - don't expect here to be synchronized on new cfg! // Z might already get a lock } } if(id == 'Z'){ mySleep(666); synchronized (cfg){ cfg.a=id; mySleep(100); cfg.b=id; } } System.out.println("Doer "+id+" end"); cfg.log(); } } public static void main(String[] args) throws InterruptedException { Doer X = new Doer('X'); Doer Y = new Doer('Y'); Doer Z = new Doer('Z'); X.start(); Y.start(); Z.start(); } }
la source
AtomicReference s'adapte à vos besoins.
De la documentation java sur le paquet atomique :
boolean compareAndSet(expectedValue, updateValue);
Exemple de code:
String initialReference = "value 1"; AtomicReference<String> someRef = new AtomicReference<String>(initialReference); String newReference = "value 2"; boolean exchanged = someRef.compareAndSet(initialReference, newReference); System.out.println("exchanged: " + exchanged);
Dans l'exemple ci-dessus, vous remplacez
String
par le vôtreObject
Question SE connexe:
Quand utiliser AtomicReference en Java?
la source
Si
o
jamais ne change pendant la durée de vie d'une instance deX
, la deuxième version est de meilleur style, que la synchronisation soit impliquée ou non.Maintenant, il est impossible de répondre s'il y a quelque chose qui ne va pas avec la première version sans savoir ce qui se passe d'autre dans cette classe. J'aurais tendance à être d'accord avec le compilateur pour dire qu'il semble sujet aux erreurs (je ne répéterai pas ce que les autres ont dit).
la source
Juste en ajoutant mes deux cents: j'ai eu cet avertissement lorsque j'ai utilisé un composant qui est instancié via le concepteur, donc son champ ne peut pas vraiment être final, car le constructeur ne peut pas prendre de paramètres. En d'autres termes, j'avais un champ quasi-final sans le mot-clé final.
Je pense que c'est pour cela que c'est juste un avertissement: vous faites probablement quelque chose de mal, mais cela peut aussi être juste.
la source