Como sincronizar uma variável estática entre threads executando instâncias diferentes de uma classe em Java?

120

Eu sei que o uso da synchronizepalavra - chave antes de um método traz sincronização para esse objeto. Ou seja, 2 threads executando a mesma instância do objeto serão sincronizados.

No entanto, como a sincronização está no nível do objeto, 2 threads executando instâncias diferentes do objeto não serão sincronizados. Se tivermos uma variável estática em uma classe Java chamada pelo método, gostaríamos que ela fosse sincronizada entre instâncias da classe. As duas instâncias estão em execução em 2 threads diferentes.

Podemos alcançar a sincronização da seguinte maneira?

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

É verdade que, uma vez que definimos um objeto lockque é estático e estamos usando a palavra-chave synchronizedpara esse bloqueio, a variável estática countagora está sincronizada entre instâncias da classe Test?

Anônimo
fonte
4
todas essas respostas são inúteis, a menos que o objeto de bloqueio seja declarado FINAL!
Veja também java.util.concurrent.atomic.AtomicInteger
RoundSparrow hilltx

Respostas:

193

Existem várias maneiras de sincronizar o acesso a uma variável estática.

  1. Use um método estático sincronizado. Isso sincroniza no objeto de classe.

    public class Test {
        private static int count = 0;
    
        public static synchronized void incrementCount() {
            count++;
        }
    } 
  2. Sincronize explicitamente no objeto de classe.

    public class Test {
        private static int count = 0;
    
        public void incrementCount() {
            synchronized (Test.class) {
                count++;
            }
        }
    } 
  3. Sincronize em outro objeto estático.

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

O método 3 é o melhor em muitos casos, porque o objeto de bloqueio não é exposto fora da sua classe.

Darron
fonte
1
1. o primeiro nem precisa de um objeto de bloqueio, não deveria ser o melhor?
Baiyan Huang
4
2. declarar count como volátil também funcionaria, pois o volátil garante que a variável seja sincronizada.
Baiyan Huang
9
O motivo 3 é o melhor: qualquer bit aleatório de código pode ser sincronizado Test.classe potencialmente estragar o seu dia. Além disso, a inicialização da classe é executada com um bloqueio na classe realizada; portanto, se você tiver inicializadores malucos, poderá se dar dores de cabeça. volatilenão ajuda count++porque é uma sequência de leitura / modificação / gravação. Conforme observado em uma resposta diferente, java.util.concurrent.atomic.AtomicIntegerprovavelmente é a escolha certa aqui.
Fadden
4
Não se esqueça de sincronizar a operação de leitura na contagem, se você quiser ler o valor correto, conforme definido por outros threads. Declarar volátil (além da gravação sincronizada) também ajudará com isso.
N0rm1e
1
@Ferrybig não, você está travando Test.class. thisseria o bloqueio para métodos não estáticos sincronizados
#
64

Se você está simplesmente compartilhando um contador, considere usar um AtomicInteger ou outra classe adequada do pacote java.util.concurrent.atomic:

public class Test {

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

    public void foo() {  
        count.incrementAndGet();
    }  
}
Kevin
fonte
3
Está disponível no java 1.5, não no 1.6.
Pawel
4

Sim, é verdade.

Se você criar duas instâncias da sua classe

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

Então t1.foo e t2.foo são sincronizados no mesmo objeto estático e, portanto, se bloqueiam.

riquezas
fonte
um bloqueará o outro, e não o outro de uma só vez, se for tomado cuidado.
Jafar Ali # /
0

Você pode sincronizar seu código pela classe. Isso seria mais simples.

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

Espero que você encontre esta resposta útil.

Jafar Ali
fonte
2
Isso funcionará, mas como mencionado em outro lugar pelo @Fadden, tenha cuidado para que qualquer outro encadeamento também possa ser sincronizado Test.classe afetar o comportamento. É por isso que a sincronização ativada lockpode ser preferida.
Sbk 03/09/19
O que você está dizendo está correto. É por isso que mencionei claramente que o exposto acima é a abordagem mais simples.
Jafar Ali
0

Também podemos usar o ReentrantLock para obter a sincronização para variáveis ​​estáticas.

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
fonte