¿Cómo sincronizar una variable estática entre subprocesos que ejecutan diferentes instancias de una clase en Java?

120

Sé que usar la synchronizepalabra clave antes de un método trae sincronización a ese objeto. Es decir, se sincronizarán 2 subprocesos que ejecutan la misma instancia del objeto.

Sin embargo, dado que la sincronización se realiza a nivel de objeto, no se sincronizarán 2 subprocesos que ejecutan diferentes instancias del objeto. Si tenemos una variable estática en una clase Java a la que llama el método, nos gustaría que se sincronizara en todas las instancias de la clase. Las dos instancias se ejecutan en 2 subprocesos diferentes.

¿Podemos lograr la sincronización de la siguiente manera?

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

¿Es cierto que, dado que hemos definido un objeto lockque es estático y estamos usando la palabra clave synchronizedpara ese bloqueo, la variable estática countahora está sincronizada en todas las instancias de la clase Test?

Anónimo
fuente
4
Todas estas respuestas son INÚTILES a menos que el objeto de bloqueo se declare FINAL.
También mire java.util.concurrent.atomic.AtomicInteger
RoundSparrow hilltx

Respuestas:

193

Hay varias formas de sincronizar el acceso a una variable estática.

  1. Utilice un método estático sincronizado. Esto se sincroniza con el objeto de clase.

    public class Test {
        private static int count = 0;
    
        public static synchronized void incrementCount() {
            count++;
        }
    } 
  2. Sincronizar explícitamente en el objeto de clase.

    public class Test {
        private static int count = 0;
    
        public void incrementCount() {
            synchronized (Test.class) {
                count++;
            }
        }
    } 
  3. Sincronice con algún otro objeto estático.

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

El método 3 es el mejor en muchos casos porque el objeto de bloqueo no se expone fuera de su clase.

Darron
fuente
1
1. El primero ni siquiera necesita un objeto de bloqueo, ¿no debería ser el mejor?
Baiyan Huang
4
2. Declarar el recuento como volátil también funcionaría, ya que volátil asegura que la variable esté sincronizada.
Baiyan Huang
9
La razón por la que el número 3 es el mejor es que cualquier fragmento de código aleatorio podría sincronizarse Test.classy arruinarle el día. Además, la inicialización de clases se ejecuta con un bloqueo en la clase retenida, por lo que si tienes inicializadores de clases locos, puedes darte dolores de cabeza. volatileno ayuda count++porque es una secuencia de lectura / modificación / escritura. Como se señaló en una respuesta diferente, java.util.concurrent.atomic.AtomicIntegerprobablemente sea la elección correcta aquí.
fadden
4
No olvide sincronizar la operación de lectura en el recuento si desea leer el valor correcto según lo establecido por otros hilos. Declararlo volátil (además de la escritura sincronizada) también ayudará con esto.
n0rm1e
1
@Ferrybig no, estás bloqueando Test.class. thissería el bloqueo para métodos sincronizados no estáticos
user3237736
64

Si simplemente está compartiendo un contador, considere usar un AtomicInteger u otra clase adecuada del paquete java.util.concurrent.atomic:

public class Test {

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

    public void foo() {  
        count.incrementAndGet();
    }  
}
Kevin
fuente
3
Está disponible en Java 1.5, no en 1.6.
Pawel
4

Si es cierto.

Si crea dos instancias de su clase

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

Entonces t1.foo y t2.foo se sincronizan en el mismo objeto estático y, por lo tanto, se bloquean entre sí.

ricos
fuente
uno bloqueará al otro, no el uno al otro a la vez si se cuida.
Jafar Ali
0

Puede sincronizar su código sobre la clase. Eso sería lo más sencillo.

   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 encuentre útil esta respuesta.

Jafar Ali
fuente
2
Esto funcionará, pero como lo mencionó @Fadden en otra parte, tenga en cuenta que cualquier otro hilo también podría sincronizarse Test.classy afectar el comportamiento. Esta es la razón por la que lockpodría preferirse la sincronización .
sbk
Lo que dices es correcto. Es por eso que menciono claramente que lo anterior es el enfoque más simple.
Jafar Ali
0

También podemos usar ReentrantLock para lograr la sincronización de variables 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
fuente