web-dev-qa-db-fra.com

Les getters et setters doivent-ils être synchronisés?

private double value;

public synchronized void setValue(double value) {
    this.value = value;
}
public double getValue() {
    return this.value;
}

Dans l'exemple ci-dessus, est-il utile de synchroniser le getter?

55
DD.

Je pense qu'il vaut mieux citer Java Concurrency in Practice ici:

C'est une erreur courante de supposer que la synchronisation ne doit être utilisée que lors de l'écriture dans des variables partagées; ce n'est tout simplement pas vrai.

Pour chaque variable d'état mutable accessible par plusieurs threads, tous les accès à cette variable doivent être effectués avec le même verrou maintenu. Dans ce cas, nous disons que la variable est gardée par ce verrou.

En l'absence de synchronisation, le compilateur, le processeur et le runtime peuvent carrément faire des choses étranges dans l'ordre dans lequel les opérations semblent s'exécuter. Les tentatives de raisonnement sur l'ordre dans lequel les actions de mémoire "doivent" se produire dans les programmes multithread synchronisés de manière insuffisante seront presque certainement incorrectes.

Normalement, vous n'avez pas à être aussi prudent avec les primitives, donc si ce serait un int ou un boolean cela pourrait être que:

Lorsqu'un thread lit une variable sans synchronisation, il peut voir une valeur périmée, mais au moins il voit une valeur qui a été réellement placée là par un thread plutôt que par une valeur aléatoire.

Cela n'est cependant pas vrai pour les opérations 64 bits, par exemple sur long ou double si elles ne sont pas déclarées volatile:

Le modèle de mémoire Java nécessite que les opérations d'extraction et de stockage soient atomiques, mais pour les variables longues et doubles non volatiles, la JVM est autorisée à traiter une lecture ou une écriture 64 bits comme deux opérations 32 bits distinctes). Si les lectures et écritures se produisent dans des threads différents, il est donc possible de lire un long non volatile et de récupérer les 32 bits hauts d'une valeur et les 32 bits bas d'une autre.

Ainsi, même si vous ne vous souciez pas des valeurs périmées, il n'est pas sûr d'utiliser des variables partagées longues et doubles mutables dans les programmes multithreads, sauf si elles sont déclarées volatiles ou protégées par un verrou.

73
Konrad Reiche

Permettez-moi de vous montrer par exemple quel est un moyen légal pour un JIT de compiler votre code. Vous écrivez:

while (myBean.getValue() > 1.0) {
  // perform some action
  Thread.sleep(1);
}

JIT compile:

if (myBean.getValue() > 1.0) 
  while (true) {
    // perform some action
    Thread.sleep(1);
  }

Dans des scénarios légèrement différents, même le compilateur Java pourrait produire un bytecode similaire (il n'aurait qu'à éliminer la possibilité d'une répartition dynamique vers un autre getValue). Ceci est un exemple de manuel de levage.

Pourquoi est-ce légal? Le compilateur a le droit de supposer que le résultat de myBean.getValue() ne peut jamais changer lors de l'exécution du code ci-dessus. Sans synchronized, il est permis d'ignorer toutes les actions d'autres threads.

16
Marko Topolnik

La raison ici est de se prémunir contre tout autre thread mettant à jour la valeur lorsqu'un thread est en train de lire et d'éviter ainsi d'effectuer toute action sur la valeur périmée.

Ici, la méthode get acquiert un verrouillage intrinsèque sur "ceci" et donc tout autre thread qui pourrait tenter de définir/mettre à jour en utilisant la méthode setter devra attendre d'acquérir le verrouillage sur "this" pour entrer dans la méthode setter qui est déjà acquise par le thread exécutant get .

C'est pourquoi il est recommandé de suivre la pratique d'utiliser le même verrou lors de l'exécution de toute opération sur un état mutable.

Rendre le champ volatile fonctionnera ici car il n'y a pas d'instructions composées.


Il est important de noter que les méthodes synchronisées utilisent un verrouillage intrinsèque qui est "ceci". Donc, obtenir et définir les deux étant synchronisés signifie que tout thread entrant dans la méthode devra acquérir un verrou sur cela.


Lors de l'exécution d'opérations 64 bits non atomiques, une attention particulière doit être prise. Des extraits de Java Concurrence en pratique pourrait être utile ici pour comprendre la situation -

"Le modèle de mémoire Java nécessite que les opérations d'extraction et de stockage soient atomiques, mais pour les variables longues et doubles non volatiles, la JVM est autorisée à traiter une lecture ou une écriture 64 bits comme deux 32 bits distincts Si les lectures et les écritures se produisent dans des threads différents, il est donc possible de lire un long non volatile et de récupérer les 32 bits supérieurs d'une valeur et les 32 bits inférieurs d'une autre. Ainsi, même si vous ne vous en souciez pas en ce qui concerne les valeurs périmées, il n'est pas sûr d'utiliser des variables partagées longues et doubles mutables dans des programmes multithreads, sauf si elles sont déclarées volatiles ou protégées par un verrou. "

1
Upendra Upadhyay

Peut-être que pour quelqu'un, ce code a l'air horrible, mais il fonctionne très bien.

  private Double value;
  public  void setValue(Double value){
    updateValue(value, true);
  }
  public Double getValue(){
      return updateValue(value, false);
  }
  private double updateValue(Double value,boolean set){
    synchronized(MyClass.class){
      if(set)
        this.value = value;
      return value;
    }
  }
0
Adam111p