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

120

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

Однако, поскольку синхронизация происходит на уровне объекта, 2 потока, выполняющие разные экземпляры объекта, не будут синхронизированы. Если у нас есть статическая переменная в классе Java, вызываемая методом, мы хотели бы, чтобы она синхронизировалась между экземплярами класса. Два экземпляра работают в 2 разных потоках.

Можно ли добиться синхронизации следующим образом?

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 является лучшим во многих случаях, потому что объект блокировки не отображается за пределами вашего класса.

Darron
источник
1
1. первому даже не нужен объект блокировки, разве он не лучший?
Baiyan Huang
4
2. Объявление count as volatile также будет работать, поскольку volatile гарантирует, что переменная синхронизирована.
Baiyan Huang
9
Причина №3 - лучшая в том, что любой случайный фрагмент кода может синхронизироваться Test.classи потенциально испортить вам день. Кроме того, инициализация класса выполняется с блокировкой удерживаемого класса, поэтому, если у вас есть сумасшедшие инициализаторы класса, вы можете причинить себе головную боль. volatileне помогает, count++потому что это последовательность чтения / изменения / записи. Как отмечено в другом ответе, java.util.concurrent.atomic.AtomicIntegerэто, вероятно, правильный выбор.
fadden
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();
    }  
}
Kevin
источник
3
Он доступен в java 1.5, но не в 1.6.
Павел
4

Да, это правда.

Если вы создадите два экземпляра своего класса

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

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

Richs
источник
один блокирует другой, а не друг друга сразу, если о них позаботиться.
Джафар Али
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 03
То, что вы говорите, правильно. Вот почему я четко упомянул, что это самый простой подход.
Джафар Али
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();
    }  
}
Сунил
источник