¿Por qué esta clase no es segura para subprocesos?

94
class ThreadSafeClass extends Thread
{
     private static int count = 0;

     public synchronized static void increment()
     {
         count++;
     }

     public synchronized void decrement()
     {
         count--;
     }
}

¿Alguien puede explicar por qué la clase anterior no es segura para subprocesos?

das kinder
fuente
6
No sé sobre Java, pero parece que cada uno de esos métodos es seguro para subprocesos individualmente , pero podría tener un subproceso en cada uno de los métodos al mismo tiempo. Quizás si tiene un solo método que toma un bool ( increment), sería seguro para subprocesos. O si usó algún objeto de bloqueo. Como dije, no sé sobre Java; mi comentario proviene del conocimiento de C #.
Wai Ha Lee
Tampoco conozco muy bien Java, pero para sincronizar el acceso a una variable estática, synchronizeddebe usarse solo en métodos estáticos. Entonces, en mi opinión, incluso si elimina el incrementmétodo, todavía no es seguro para subprocesos ya que dos instancias (que solo tienen acceso sincronizado a través de la misma instancia) pueden llamar al método simultáneamente.
Onur
4
Es seguro para subprocesos siempre que nunca cree una instancia de la clase.
Benjamin Gruenbaum
1
¿Por qué crees que no es seguro para subprocesos?
Raedwald

Respuestas:

134

Dado que el incrementmétodo staticse sincronizará en el objeto de clase para ThreadSafeClass. El decrementmétodo no es estático y se sincronizará en la instancia utilizada para llamarlo. Es decir, se sincronizarán en diferentes objetos y, por lo tanto, dos subprocesos diferentes pueden ejecutar los métodos al mismo tiempo. Dado que las operaciones ++y --no son atómicas, la clase no es segura para subprocesos.

Además, dado que countes static, modificarlo a partir del método de instanciadecrement sincronizada no es seguro, ya que se puede llamar en diferentes instancias y modificar simultáneamente de esa manera.count

K Erlandsson
fuente
12
Puede agregar, dado que countes incorrecto statictener un método de instancia decrement(), incluso si no hay un static increment()método, ya que dos subprocesos pueden invocar decrement()en diferentes instancias modificando el mismo contador al mismo tiempo.
Holger
1
Esa es posiblemente una buena razón para preferir usar synchronizedbloques en general (incluso en todo el contenido del método) en lugar de usar el modificador en el método, es decir, synchronized(this) { ... }y synchronized(ThreadSafeClass.class) { ... }.
Bruno
++y --no son atómicos, incluso encendidosvolatile int . Synchronizedse ocupa del problema de lectura / actualización / escritura con ++/ --, pero la staticpalabra clave es, bueno, la clave aquí. ¡Buena respuesta!
Chris Cirefice
Re, modificar [un campo estático] desde ... un método de instancia sincronizado es incorrecto : no hay nada intrínsecamente incorrecto en acceder a una variable estática desde dentro de un método de instancia, y tampoco hay nada intrínsecamente incorrecto en acceder a ella desde un synchronizedmétodo de instancia. Simplemente no espere que "sincronizado" en el método de instancia proporcione protección para los datos estáticos. El único problema aquí es lo que dijo en su primer párrafo: los métodos utilizan diferentes bloqueos en un intento de proteger los mismos datos, y eso, por supuesto, no proporciona ninguna protección.
Solomon Slow
23

Tiene dos métodos sincronizados, pero uno de ellos es estático y el otro no. Al acceder a un método sincronizado, según su tipo (estático o no estático), se bloqueará un objeto diferente. Para un método estático, se colocará un bloqueo en el objeto Class, mientras que para el bloque no estático, se colocará un bloqueo en la instancia de la clase que ejecuta el método. Debido a que tiene dos objetos bloqueados diferentes, puede tener dos subprocesos que modifiquen el mismo objeto simultáneamente.

Slimu
fuente
14

¿Alguien puede explicar por qué la clase anterior no es segura para subprocesos?

  • increment siendo estático, la sincronización se realizará en la propia clase.
  • decremental no ser estático, la sincronización se realizará en la instanciación del objeto, pero eso no asegura nada ya que countes estático.

Me gustaría agregar que para declarar un contador seguro para subprocesos, creo que la forma más simple es usar en AtomicIntegerlugar de un int primitivo.

Déjame redirigirte a la java.util.concurrent.atomicinformación del paquete.

Jean-François Savard
fuente
7

Las respuestas de otros son bastante buenas explicando la razón. Solo agrego algo para resumir synchronized:

public class A {
    public synchronized void fun1() {}

    public synchronized void fun2() {}

    public void fun3() {}

    public static synchronized void fun4() {}

    public static void fun5() {}
}

A a1 = new A();

synchronizeden fun1y fun2se sincroniza a nivel de objeto de instancia. synchronizedon fun4se sincroniza a nivel de objeto de clase. Lo que significa:

  1. Cuando 2 hilos llamen a1.fun1()al mismo tiempo, se bloqueará la última llamada.
  2. Cuando el hilo 1 llama a1.fun1()y el hilo 2 llama a1.fun2()al mismo tiempo, la última llamada será bloqueada.
  3. Cuando el hilo 1 llama a1.fun1()y el hilo 2 llamaa1.fun3() al mismo tiempo, sin bloqueo, los 2 métodos se ejecutarán al mismo tiempo.
  4. Cuando el hilo 1 llamada A.fun4(), si llaman otros hilos A.fun4()o A.fun5()al mismo tiempo, estas últimas llamadas serán bloqueados desde synchronizedel fun4es el nivel de clase.
  5. Cuando el hilo 1 llama A.fun4(), el hilo 2 llama a1.fun1()al mismo tiempo, sin bloqueo, los 2 métodos se ejecutarán al mismo tiempo.
coderz
fuente
6
  1. decrementestá bloqueando algo diferente para incrementque no se impidan que se ejecuten.
  2. Llamar decrementa una instancia es bloquear una cosa diferente a llamar decrementa otra instancia, pero están afectando lo mismo.

La primera significa que las llamadas superpuestas a incrementy decrementpodrían resultar en una cancelación (correcta), un incremento o una disminución.

El segundo significa que dos llamadas superpuestas a decrementen diferentes instancias podrían resultar en una disminución doble (correcta) o una disminución simple.

Jon Hanna
fuente
4

Dado que hay dos métodos diferentes, uno es el nivel de instancia y el otro es el nivel de clase, por lo que debe bloquear 2 objetos diferentes para que sea ThreadSafe

jaleel_quest
fuente
1

Como se explica en otras respuestas, su código no es seguro para subprocesos, ya que el método estático increment()bloquea el monitor de clase y el método no estático decrement()bloquea el monitor de objeto.

Para este ejemplo de código, existe una mejor solución sin el synchronzeduso de palabras clave. Tienes que usar AtomicInteger para lograr la seguridad de Thread.

Uso seguro para subprocesos AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;

class ThreadSafeClass extends Thread {

    private static AtomicInteger count = new AtomicInteger(0);

    public static void increment() {
        count.incrementAndGet();
    }

    public static void decrement() {
        count.decrementAndGet();
    }

    public static int value() {
        return count.get();
    }

}
Ravindra babu
fuente