Як синхронізувати статичну змінну серед потоків, що виконують різні екземпляри класу на Java?


120

Я знаю, що використання synchronizeключового слова перед методом приносить синхронізацію з цим об’єктом. Тобто, 2 потоки, що виконують один і той же екземпляр об'єкта, будуть синхронізовані.

Однак, оскільки синхронізація знаходиться на рівні об'єкта, 2 потоки, що виконують різні екземпляри об'єкта, не будуть синхронізовані. Якщо у нас є статична змінна в класі Java, який викликається методом, ми хотіли б, щоб вона була синхронізована через екземпляри класу. Два екземпляри працюють у двох різних потоках.

Чи можемо ми досягти синхронізації таким чином?

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

Це правда, що оскільки ми визначили об'єкт, lockякий є статичним, і ми використовуємо ключове слово synchronizedдля цього блокування, статична змінна countзараз синхронізується через екземпляри класу Test?


4
всі ці відповіді НЕ ВИКОРИСТАННІ, якщо об'єкт блокування не оголошено ЗАКОННИМ!

Також дивіться на java.util.concurrent.atomic.AtomicInteger
RoundSparrow hilltx

Відповіді:


193

Існує кілька способів синхронізувати доступ до статичної змінної.

  1. Використовуйте синхронізований статичний метод. Це синхронізується на об’єкт класу.

    public class Test {
        private static int count = 0;
    
        public static synchronized void incrementCount() {
            count++;
        }
    } 
  2. Явно синхронізується на об’єкті класу.

    public class Test {
        private static int count = 0;
    
        public void incrementCount() {
            synchronized (Test.class) {
                count++;
            }
        }
    } 
  3. Синхронізуйте на якомусь іншому статичному об'єкті.

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

Спосіб 3 найкращий у багатьох випадках, оскільки об'єкт блокування не піддається впливу вашого класу.


1
1. Перший навіть не потребує об'єкта блокування, чи не найкраще це?
Байян Хуанг

4
2. оголосити підрахунок як мінливим також буде спрацьовувати, оскільки мінливий переконує, що змінна синхронізована.
Байян Хуанг

9
Причина №3 найкраща в тому, що будь-який випадковий біт коду може синхронізуватися Test.classта потенційно зіпсувати ваш день. Крім того, ініціалізація класів працює з блокуванням утримуваного класу, тому якщо у вас є ініціалізатори шаленого класу, ви можете надати собі головний біль. volatileне допомагає, count++оскільки це послідовність читання / зміни / запису. Як зазначається в іншій відповіді, java.util.concurrent.atomic.AtomicIntegerтут, ймовірно, правильний вибір.
вівторок

4
Не забувайте синхронізувати операцію читання підрахунку, якщо ви хочете прочитати правильне значення, встановлене іншими потоками. Оголошення його мінливим (крім синхронізованого запису) також допоможе в цьому.
n0rm1e

1
@Ferrybig ні, ви замикаєтесь Test.class. thisбуде блокуванням для синхронізованих нестатичних методів
user3237736

64

Якщо ви просто ділите лічильник, подумайте про використання AtomicInteger або іншого відповідного класу з пакету java.util.concurrent.atomic:

public class Test {

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

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

3
Він доступний у java 1.5, а не в 1.6.
Pawel

4

Так, це правда.

Якщо ви створюєте два екземпляри свого класу

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

Тоді t1.foo і t2.foo обидва синхронізуються на одному статичному об'єкті і, отже, блокують один одного.


один заблокує інший, а не один одного одразу, якщо доглядати.
Джафар Алі

0

Ви можете синхронізувати свій код за класом. Це було б найпростіше.

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

Сподіваємось, Ви вважаєте цю відповідь корисною.


2
Це буде спрацьовувати, але як зазначає інше @Fadden, будьте обережні, що будь-який інший потік також може синхронізуватися Test.classта вплинути на поведінку. Ось чому lockможе бути кращим синхронізація .
sbk

Те, що ви говорите, правильно. Саме тому я чітко згадую, що вищесказане - найпростіший підхід.
Джафар Алі

0

Ми також можемо використовувати ReentrantLock для досягнення синхронізації статичних змінних.

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();
    }  
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.