web-dev-qa-db-fra.com

Pourquoi cette classe n'est-elle pas sûre pour les threads?

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

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

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

Quelqu'un peut-il expliquer pourquoi la classe ci-dessus n'est pas sécurisée pour les threads?

94
das kinder

Puisque la méthode increment est static, elle se synchronisera sur l'objet classe pour le ThreadSafeClass. La méthode decrement n'est pas statique et se synchronisera sur l'instance utilisée pour l'appeler. C'est-à-dire qu'ils se synchroniseront sur différents objets et donc deux threads différents pourront exécuter les méthodes en même temps. Depuis le ++ et -- les opérations ne sont pas atomiques, la classe n'est pas thread-safe.

De plus, puisque count est static, la modifier à partir de decrement qui est une méthode instance synchronisée n'est pas sûre car elle peut être appelée sur différentes instances et modifier count simultanément de cette façon.

134
K Erlandsson

Vous avez deux méthodes synchronisées, mais l'une d'entre elles est statique et l'autre ne l'est pas. Lors de l'accès à une méthode synchronisée, en fonction de son type (statique ou non statique), un objet différent sera verrouillé. Pour une méthode statique, un verrou sera placé sur l'objet Class, tandis que pour le bloc non statique, un verrou sera placé sur l'instance de la classe qui exécute la méthode. Parce que vous avez deux objets verrouillés différents, vous pouvez avoir deux threads qui modifient le même objet simultanément.

23
Slimu

Quelqu'un peut-il expliquer pourquoi la classe ci-dessus n'est pas thread-safe?

  • increment étant statique, la synchronisation se fera sur la classe elle-même.
  • decrement n'étant pas statique, la synchronisation se fera sur l'instanciation de l'objet, mais cela ne sécurise rien car count est statique.

Je voudrais ajouter que pour déclarer un compteur thread-safe, je pense que la façon la plus simple est d'utiliser AtomicInteger au lieu d'un int primitif.

Permettez-moi de vous rediriger vers le Java.util.concurrent.atomic package-info.

14

Les réponses des autres sont assez bonnes pour expliquer la raison. Je viens d'ajouter quelque chose pour résumer 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();

synchronized sur fun1 et fun2 est synchronisé au niveau de l'objet d'instance. synchronized on fun4 est synchronisé au niveau de l'objet classe. Ce qui signifie:

  1. Lorsque 2 threads appellent a1.fun1() en même temps, ce dernier appel sera bloqué.
  2. Lorsque le thread 1 appelle a1.fun1() et le thread 2 appelle a1.fun2() en même temps, ce dernier appel est bloqué.
  3. Lorsque le thread 1 appelle a1.fun1() et le thread 2 appelle a1.fun3() en même temps, aucun blocage, les 2 méthodes seront exécutées en même temps.
  4. Lorsque le thread 1 appelle A.fun4(), si d'autres threads appellent A.fun4() ou A.fun5() en même temps, ces derniers appels seront bloqués car synchronized sur fun4 Est au niveau de la classe.
  5. Lorsque le thread 1 appelle A.fun4(), le thread 2 appelle a1.fun1() en même temps, aucun blocage, les 2 méthodes seront exécutées en même temps.
7
coderz
  1. decrement se verrouille sur une chose différente de increment afin qu'ils ne s'empêchent pas de s'exécuter.
  2. Appeler decrement sur une instance revient à verrouiller une autre chose que d'appeler decrement sur une autre instance, mais ils affectent la même chose.

Le premier signifie que les appels qui se chevauchent vers increment et decrement peuvent entraîner une annulation (correcte), un incrément ou une décrémentation.

Le second signifie que deux appels qui se chevauchent vers decrement sur des instances différentes peuvent entraîner un décrément double (correct) ou un décrément unique.

6
Jon Hanna

Comme expliqué dans d'autres réponses, votre code est pas Thread safe puisque la méthode statique increment() verrouille Moniteur de classe et méthode non statique decrement() verrouille le moniteur d'objet.

Pour cet exemple de code, une meilleure solution existe sans utilisation du mot clé synchronzed. Vous devez utiliser AtomicInteger pour assurer la sécurité des threads.

Thread safe en utilisant 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();
    }

}
1
Ravindra babu