Comment synchroniser une variable statique parmi les threads exécutant différentes instances d'une classe en Java?

120

Je sais que l'utilisation du synchronizemot - clé avant une méthode apporte la synchronisation à cet objet. Autrement dit, 2 threads exécutant la même instance de l'objet seront synchronisés.

Cependant, puisque la synchronisation est au niveau de l'objet, 2 threads exécutant différentes instances de l'objet ne seront pas synchronisés. Si nous avons une variable statique dans une classe Java qui est appelée par la méthode, nous aimerions qu'elle soit synchronisée entre les instances de la classe. Les deux instances s'exécutent dans 2 threads différents.

Pouvons-nous réaliser la synchronisation de la manière suivante?

public class Test  
{  
   private static int count = 0;  
   private static final Object lock= new Object();    
   public synchronized void foo() 
  {  
      synchronized(lock)
     {  
         count++;  
     }  
  }  
}

Est-il vrai que puisque nous avons défini un objet lockqui est statique et que nous utilisons le mot-clé synchronizedpour ce verrou, la variable statique countest maintenant synchronisée entre les instances de la classe Test?

Anonyme
la source
4
toutes ces réponses sont inutiles à moins que l'objet de verrouillage ne soit déclaré FINAL!
Regardez également java.util.concurrent.atomic.AtomicInteger
RoundSparrow hilltx

Réponses:

193

Il existe plusieurs façons de synchroniser l'accès à une variable statique.

  1. Utilisez une méthode statique synchronisée. Cela se synchronise sur l'objet de classe.

    public class Test {
        private static int count = 0;
    
        public static synchronized void incrementCount() {
            count++;
        }
    } 
  2. Synchronisez explicitement sur l'objet de classe.

    public class Test {
        private static int count = 0;
    
        public void incrementCount() {
            synchronized (Test.class) {
                count++;
            }
        }
    } 
  3. Synchronisez sur un autre objet statique.

    public class Test {
        private static int count = 0;
        private static final Object countLock = new Object();
    
        public void incrementCount() {
            synchronized (countLock) {
                count++;
            }
        }
    } 

La méthode 3 est la meilleure dans de nombreux cas car l'objet de verrouillage n'est pas exposé en dehors de votre classe.

Darron
la source
1
1. Le premier n'a même pas besoin d'un objet de verrouillage, ne devrait-il pas être le meilleur?
Baiyan Huang
4
2. déclarer le décompte comme volatile fonctionne également, car volatile s'assure que la variable est synchronisée.
Baiyan Huang
9
La raison n ° 3 est la meilleure est que tout bit de code aléatoire pourrait se synchroniser Test.classet potentiellement gâcher votre journée. De plus, l'initialisation de classe s'exécute avec un verrou sur la classe détenue, donc si vous avez des initialiseurs de classe fous, vous pouvez vous donner des maux de tête. volatilen'aide pas count++car c'est une séquence de lecture / modification / écriture. Comme indiqué dans une réponse différente, java.util.concurrent.atomic.AtomicIntegerest probablement le bon choix ici.
fadden
4
N'oubliez pas de synchroniser l'opération de lecture sur le nombre si vous souhaitez lire la valeur correcte telle que définie par d'autres threads. Le déclarer volatil (en plus de l'écriture synchronisée) aidera également à cela.
n0rm1e
1
@Ferrybig non, vous vous verrouillez Test.class. thisserait le verrou pour les méthodes non statiques synchronisées
user3237736
64

Si vous partagez simplement un compteur, envisagez d'utiliser un AtomicInteger ou une autre classe appropriée du package java.util.concurrent.atomic:

public class Test {

    private final static AtomicInteger count = new AtomicInteger(0); 

    public void foo() {  
        count.incrementAndGet();
    }  
}
Kevin
la source
3
Il est disponible en java 1.5, pas en 1.6.
Pawel
4

Oui c'est vrai.

Si vous créez deux instances de votre classe

Test t1 = new Test();
Test t2 = new Test();

Ensuite, t1.foo et t2.foo se synchronisent tous les deux sur le même objet statique et se bloquent donc mutuellement.

riches
la source
l'un bloquera l'autre, pas l'un l'autre à la fois si on y prend soin.
Jafar Ali
0

Vous pouvez synchroniser votre code sur la classe. Ce serait le plus simple.

   public class Test  
    {  
       private static int count = 0;  
       private static final Object lock= new Object();    
       public synchronized void foo() 
      {  
          synchronized(Test.class)
         {  
             count++;  
         }  
      }  
    }

J'espère que vous trouverez cette réponse utile.

Jafar Ali
la source
2
Cela fonctionnera, mais comme mentionné ailleurs par @Fadden, sachez que tout autre thread pourrait également se synchroniser Test.classet affecter le comportement. C'est pourquoi la synchronisation sur lockpeut être préférée.
sbk
Ce que vous dites est correct. C'est pourquoi je mentionne clairement que ce qui précède est l'approche la plus simple.
Jafar Ali
0

Nous pouvons également utiliser ReentrantLock pour réaliser la synchronisation des variables statiques.

public class Test {

    private static int count = 0;
    private static final ReentrantLock reentrantLock = new ReentrantLock(); 
    public void foo() {  
        reentrantLock.lock();
        count = count + 1;
        reentrantLock.unlock();
    }  
}
Sunil
la source