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){
}
}
}
}
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) {
}
}
}
}
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) {
synchronized (LOCK3) {
synchronized (LOCK2) {
}
}
}
}
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
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é.