Come sincronizzare una variabile statica tra thread che eseguono istanze diverse di una classe in Java?


120

So che l'uso della synchronizeparola chiave prima di un metodo porta la sincronizzazione a quell'oggetto. Ovvero, verranno sincronizzati 2 thread che eseguono la stessa istanza dell'oggetto.

Tuttavia, poiché la sincronizzazione è a livello di oggetto, 2 thread che eseguono istanze diverse dell'oggetto non verranno sincronizzati. Se abbiamo una variabile statica in una classe Java chiamata dal metodo, vorremmo che fosse sincronizzata tra le istanze della classe. Le due istanze vengono eseguite in 2 thread diversi.

Possiamo ottenere la sincronizzazione nel modo seguente?

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

È vero che poiché abbiamo definito un oggetto lockstatico e stiamo usando la parola chiave synchronizedper quel blocco, la variabile statica countè ora sincronizzata tra le istanze di classe Test?


4
tutte queste risposte sono INUTILI a meno che l'oggetto lock non sia dichiarato FINAL!

Guarda anche java.util.concurrent.atomic.AtomicInteger
RoundSparrow hilltx

Risposte:


193

Esistono diversi modi per sincronizzare l'accesso a una variabile statica.

  1. Usa un metodo statico sincronizzato. Questo si sincronizza sull'oggetto classe.

    public class Test {
        private static int count = 0;
    
        public static synchronized void incrementCount() {
            count++;
        }
    } 
  2. Sincronizza esplicitamente sull'oggetto classe.

    public class Test {
        private static int count = 0;
    
        public void incrementCount() {
            synchronized (Test.class) {
                count++;
            }
        }
    } 
  3. Sincronizza su qualche altro oggetto statico.

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

Il metodo 3 è il migliore in molti casi perché l'oggetto lock non è esposto al di fuori della tua classe.


1
1. il primo non ha nemmeno bisogno di un oggetto di blocco, non dovrebbe essere il migliore?
Baiyan Huang

4
2. Anche dichiarare count come volatile funzionerebbe, poiché volatile si assicura che la variabile sia sincronizzata.
Baiyan Huang

9
Il motivo n. 3 è il migliore è che qualsiasi bit casuale di codice potrebbe sincronizzarsi Test.classe potenzialmente rovinare la tua giornata. Inoltre, l'inizializzazione della classe viene eseguita con un blocco sulla classe mantenuta, quindi se hai inizializzatori di classe pazzi puoi farti venire il mal di testa. volatilenon aiuta count++perché è una sequenza di lettura / modifica / scrittura. Come notato in una risposta diversa, java.util.concurrent.atomic.AtomicIntegerè probabilmente la scelta giusta qui.
fadden

4
Non dimenticare di sincronizzare l'operazione di lettura sul conteggio se vuoi leggere il valore corretto impostato da altri thread. Anche dichiararlo volatile (oltre alla scrittura sincronizzata) aiuterà in questo.
n0rm1e

1
@Ferrybig no, ti stai bloccando Test.class. thissarebbe il blocco per i metodi non statici sincronizzati
user3237736

64

Se stai semplicemente condividendo un contatore, considera l'utilizzo di un AtomicInteger o un'altra classe adatta dal pacchetto java.util.concurrent.atomic:

public class Test {

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

    public void foo() {  
        count.incrementAndGet();
    }  
}

3
È disponibile in java 1.5, non in 1.6.
Pawel

4

Si è vero.

Se crei due istanze della tua classe

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

Quindi t1.foo e t2.foo si sincronizzano entrambi sullo stesso oggetto statico e quindi si bloccano a vicenda.


uno bloccherà l'altro, non l'un l'altro contemporaneamente se ci si prende cura.
Jafar Ali

0

Puoi sincronizzare il tuo codice sulla classe. Sarebbe più semplice.

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

Spero che trovi utile questa risposta.


2
Funzionerà, ma come menzionato altrove da @Fadden, fai attenzione che anche qualsiasi altro thread potrebbe sincronizzarsi Test.classe influenzare il comportamento. Questo è il motivo per cui lockpotrebbe essere preferibile la sincronizzazione .
sbk

Quello che stai dicendo è corretto. Ecco perché dico chiaramente che quanto sopra è l'approccio più semplice.
Jafar Ali

0

Possiamo anche utilizzare ReentrantLock per ottenere la sincronizzazione per le variabili statiche.

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();
    }  
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.