Jak zsynchronizować zmienną statyczną między wątkami obsługującymi różne instancje klasy w Javie?

120

Wiem, że użycie synchronizesłowa kluczowego przed metodą powoduje synchronizację z tym obiektem. Oznacza to, że zostaną zsynchronizowane 2 wątki z tym samym wystąpieniem obiektu.

Ponieważ jednak synchronizacja jest na poziomie obiektu, 2 wątki z różnymi instancjami obiektu nie zostaną zsynchronizowane. Jeśli w klasie Java mamy zmienną statyczną, która jest wywoływana przez metodę, chcielibyśmy, aby była zsynchronizowana między instancjami tej klasy. Te dwie instancje działają w 2 różnych wątkach.

Czy możemy osiągnąć synchronizację w następujący sposób?

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

Czy to prawda, że ​​skoro zdefiniowaliśmy obiekt, lockktóry jest statyczny i używamy słowa kluczowego synchronizeddla tej blokady, zmienna statyczna countjest teraz zsynchronizowana między instancjami klasy Test?

Anonimowy
źródło
4
wszystkie te odpowiedzi są BEZUŻYTECZNE, chyba że obiekt blokady zostanie określony jako FINAL!
Zobacz także java.util.concurrent.atomic.AtomicInteger
RoundSparrow hilltx

Odpowiedzi:

193

Istnieje kilka sposobów synchronizowania dostępu do zmiennej statycznej.

  1. Użyj zsynchronizowanej metody statycznej. Spowoduje to synchronizację w obiekcie klasy.

    public class Test {
        private static int count = 0;
    
        public static synchronized void incrementCount() {
            count++;
        }
    } 
  2. Jawna synchronizacja w obiekcie klasy.

    public class Test {
        private static int count = 0;
    
        public void incrementCount() {
            synchronized (Test.class) {
                count++;
            }
        }
    } 
  3. Zsynchronizuj z innym statycznym obiektem.

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

Metoda 3 jest najlepsza w wielu przypadkach, ponieważ obiekt blokady nie jest ujawniany poza klasą.

Darron
źródło
1
1. pierwszy nawet nie potrzebuje zamka, czyż nie powinien być najlepszy?
Baiyan Huang,
4
2. Zadeklarowanie licznika jako zmiennego również zadziała, ponieważ volatile zapewnia synchronizację zmiennej.
Baiyan Huang,
9
Powód numer 3 jest najlepszy, ponieważ każdy losowy fragment kodu może się zsynchronizować Test.classi potencjalnie zepsuć Ci dzień. Ponadto inicjalizacja klasy przebiega z blokadą trzymanej klasy, więc jeśli masz szalone inicjatory klas, możesz wywołać ból głowy. volatilenie pomaga, count++ponieważ jest to sekwencja odczytu / modyfikacji / zapisu. Jak zauważono w innej odpowiedzi, java.util.concurrent.atomic.AtomicIntegerprawdopodobnie jest to właściwy wybór.
fadden
4
Nie zapomnij zsynchronizować operacji odczytu przy liczeniu, jeśli chcesz odczytać poprawną wartość ustawioną przez inne wątki. Zadeklarowanie go jako lotnego (oprócz zsynchronizowanego zapisu) również w tym pomoże.
n0rm1e
1
@Ferrybig nie, blokujesz się Test.class. thisbyłaby blokadą dla zsynchronizowanych metod niestatycznych
user3237736
64

Jeśli po prostu udostępniasz licznik, rozważ użycie AtomicInteger lub innej odpowiedniej klasy z pakietu java.util.concurrent.atomic:

public class Test {

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

    public void foo() {  
        count.incrementAndGet();
    }  
}
Kevin
źródło
3
Jest dostępny w Javie 1.5, a nie w 1.6.
Paweł
4

Tak to jest prawda.

Jeśli utworzysz dwie instancje swojej klasy

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

Następnie t1.foo i t2.foo synchronizują się na tym samym obiekcie statycznym i blokują się wzajemnie.

bogaci
źródło
jedno będzie blokować drugie, a nie nawzajem od razu.
Jafar Ali
0

Możesz zsynchronizować swój kod z klasą. To byłoby najprostsze.

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

Mam nadzieję, że ta odpowiedź okaże się przydatna.

Jafar Ali
źródło
2
To zadziała, ale jak wspomniano w innym miejscu przez @Fadden, uważaj, że każdy inny wątek może również synchronizować się Test.classi wpływać na zachowanie. Dlatego lockpreferowana może być synchronizacja .
sbk
To, co mówisz, jest poprawne. Dlatego wyraźnie wspominam, że powyższe jest najprostszym podejściem.
Jafar Ali
0

Możemy również użyć ReentrantLock, aby uzyskać synchronizację dla zmiennych statycznych.

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
źródło