web-dev-qa-db-fra.com

Pourquoi n'est-ce pas une bonne pratique de synchroniser sur Boolean?

Mon architecte dit toujours que

Ne jamais synchroniser sur booléen

Je ne suis pas en mesure de comprendre la raison et j'apprécierais vraiment que quelqu'un puisse expliquer avec un exemple pourquoi ce n'est pas une bonne pratique. Exemple de code de référence

private Boolean isOn = false;
private String statusMessage = "I'm off";
public void doSomeStuffAndToggleTheThing(){

   // Do some stuff
   synchronized(isOn){
      if(isOn){
         isOn = false;
         statusMessage = "I'm off";
         // Do everything else to turn the thing off
      } else {
         isOn = true;
         statusMessage = "I'm on";
         // Do everything else to turn the thing on
      }
   }
}
33
Rachel

Je ne suis pas en mesure de comprendre la raison pour laquelle nous ne devons "jamais synchroniser sur Boolean"

Vous devez toujours synchronize sur un instance d'objet constante. Si vous avez synchronisé sur un objet que vous affectez (c'est-à-dire changer l'objet en un nouvel objet), il n'est pas constant et différents threads se synchroniseront sur différents objets instances. Parce qu'ils se synchronisent sur différentes instances d'objets, plusieurs threads entreront dans le bloc protégé en même temps et des conditions de concurrence se produiront. C'est la même réponse pour la synchronisation sur Long, Integer, etc.

// this is not final so it might reference different objects
Boolean isOn;
...
synchronized (isOn) {
   if (isOn) {
      // this changes the synchronized object isOn to another object
      // so another thread can then enter the synchronized with this thread
      isOn = false;

Pour aggraver les choses (comme l'a souligné @McDowell) tout Boolean créé par le biais de la mise en boîte automatique (isOn = true) est le même objet que Boolean.TRUE (ou .FALSE) qui est un singleton dans le ClassLoader à travers tous les objets. Votre objet de verrouillage doit être local à la classe dans laquelle il est utilisé, sinon vous verrouillerez le même objet singleton que d'autres classes pourraient verrouiller dans d'autres cas de verrouillage s'ils font la même erreur.

Le modèle approprié si vous avez besoin de verrouiller un booléen est de définir un private final verrouiller l'objet:

private final Object lock = new Object();
...

synchronized (lock) {
   ...

Ou vous devriez également envisager d'utiliser l'objet AtomicBoolean, ce qui signifie que vous n'aurez peut-être pas besoin de synchronize dessus.

private final AtomicBoolean isOn = new AtomicBoolean(false);
...

// if it is set to false then set it to true, no synchronization needed
if (isOn.compareAndSet(false, true)) {
    statusMessage = "I'm now on";
} else {
    // it was already on
    statusMessage = "I'm already on";
}

Dans votre cas, comme il semble que vous devez l'activer/le désactiver avec des threads, vous devrez toujours synchronize sur l'objet lock et définir le booléen et éviter la course test/set état:

synchronized (lock) {
    if (isOn) {
        isOn = false;
        statusMessage = "I'm off";
        // Do everything else to turn the thing off
    } else {
        isOn = true;
        statusMessage = "I'm on";
        // Do everything else to turn the thing on
    }
}

Enfin, si vous vous attendez à ce que statusMessage soit accessible à partir d'autres threads, alors il devrait être marqué comme volatile à moins que vous ne le fassiez également synchronize pendant le get.

61
Gray
private Boolean isOn = false;
public void doSomeStuffAndToggleTheThing(){
   synchronized(isOn){

C'est une terrible idée. isOn référencera le même objet que Boolean.FALSE qui est accessible au public. Si un autre morceau de code mal écrit décide également de verrouiller cet objet, deux transactions complètement indépendantes devront s’attendre.

Les verrouillages sont effectués sur instances d'objets , pas sur les variables qui les référencent:

enter image description here

18
McDowell

Toutes les classes wrapper sont immuables. L'une des principales raisons pour lesquelles il ne faut pas se synchroniser sur eux.

Comme si 2 threads se synchronisent sur l'objet de classe wrapper et que l'un d'eux modifie sa valeur, il sera synchronisé sur un objet nouveau/modifié et les deux threads seront synchronisés sur 2 objets différents. Ainsi, le but de la synchronisation est perdu.

1
shrishti

Je pense que votre problème est plus avec la synchronisation elle-même qu'avec la synchronisation sur les booléens. Imaginez que chaque Thread est une route, où les déclarations (voitures) se succèdent. À un moment donné, il peut y avoir une intersection: sans sémaphore, des collisions peuvent se produire. Le langage Java a une manière intégrée de décrire cela: puisque tout objet peut être une intersection, tout objet a un moniteur associé agissant comme un sémaphore. Lorsque vous utilisez synchronisé dans votre code, vous êtes en créant un sémaphore, vous devez donc utiliser le même pour toutes les routes (threads). Donc, ce problème n'est pas vraiment spécifique aux booléens car il n'y a que deux booléens, ce problème se produit chaque fois que vous synchronisez sur une variable d'instance, puis pointez la même variable à un autre objet. Ainsi, votre code est incorrect avec les booléens, mais il est tout aussi dangereux avec les entiers, les chaînes et tout objet si vous ne comprenez pas ce qui se passe.

1
Raffaele