Warum ist diese Klasse nicht threadsicher?

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

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

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

Kann jemand erklären, warum die obige Klasse nicht threadsicher ist?

das kinder
quelle
6
Ich weiß nichts über Java, aber es sieht so aus, als ob jede dieser Methoden einzeln threadsicher ist, aber Sie könnten gleichzeitig einen Thread in jeder der Methoden haben. Wenn Sie eine einzelne Methode haben, die ein bool ( increment) benötigt, ist sie möglicherweise threadsicher. Oder wenn Sie ein Sperrobjekt verwendet haben. Wie gesagt, ich weiß nichts über Java - mein Kommentar stammt aus C # -Wissen.
Wai Ha Lee
Ich kenne Java auch nicht sehr gut, aber um den Zugriff auf eine statische Variable zu synchronisieren, synchronizedsollte die nur in statischen Methoden verwendet werden. Selbst wenn Sie die incrementMethode entfernen , ist sie meiner Meinung nach immer noch nicht threadsicher, da zwei Instanzen (die nur über dieselbe Instanz synchronisierten Zugriff haben) die Methode gleichzeitig aufrufen können.
Onur
4
Es ist threadsicher, solange Sie niemals eine Instanz der Klasse erstellen.
Benjamin Gruenbaum
1
Warum denkst du, ist es nicht threadsicher?
Raedwald

Antworten:

134

Da die incrementMethode ist static, wird sie auf dem Klassenobjekt für die synchronisiert ThreadSafeClass. Die decrementMethode ist nicht statisch und wird auf der zum Aufrufen verwendeten Instanz synchronisiert. Das heißt, sie werden auf verschiedenen Objekten synchronisiert und somit können zwei verschiedene Threads die Methoden gleichzeitig ausführen. Da die Operationen ++und --nicht atomar sind, ist die Klasse nicht threadsicher.

Da dies auch der Fallcount ist static, ist das Ändern decrementeiner synchronisierten Instanzmethode unsicher, da sie auf verschiedenen Instanzen aufgerufen und countgleichzeitig auf diese Weise geändert werden kann.

K Erlandsson
quelle
12
Sie können hinzufügen, da countes falsch ist static, eine Instanzmethode zu decrement()haben, selbst wenn es keine static increment()Methode gab, da zwei Threads decrement()auf verschiedenen Instanzen aufrufen können, um denselben Zähler gleichzeitig zu ändern.
Holger
1
Dies ist möglicherweise ein guter Grund, synchronizedBlöcke im Allgemeinen (auch für den gesamten Methodeninhalt) zu verwenden, anstatt den Modifikator für die Methode zu verwenden, dh synchronized(this) { ... }und synchronized(ThreadSafeClass.class) { ... }.
Bruno
++und --sind nicht atomar, auch nicht aufvolatile int . Synchronizedkümmert sich um das Lese- / Aktualisierungs- / Schreibproblem mit ++/ --, aber das staticSchlüsselwort ist hier der Schlüssel . Gute Antwort!
Chris Cirefice
Das Ändern von [einem statischen Feld] von ... einer synchronisierten Instanzmethode ist falsch : Der Zugriff auf eine statische Variable innerhalb einer Instanzmethode ist von Natur aus falsch, und der Zugriff von einer synchronizedInstanzmethode aus ist von Natur aus falsch . Erwarten Sie nur nicht, dass "synchronisiert" auf der Instanzmethode Schutz für die statischen Daten bietet. Das einzige Problem hierbei ist das, was Sie in Ihrem ersten Absatz gesagt haben: Die Methoden verwenden unterschiedliche Sperren, um dieselben Daten zu schützen, und das bietet natürlich überhaupt keinen Schutz.
Solomon Slow
23

Sie haben zwei synchronisierte Methoden, aber eine davon ist statisch und die andere nicht. Beim Zugriff auf eine synchronisierte Methode wird je nach Typ (statisch oder nicht statisch) ein anderes Objekt gesperrt. Bei einer statischen Methode wird das Class-Objekt gesperrt, während bei dem nicht statischen Block die Instanz der Klasse, die die Methode ausführt, gesperrt wird. Da Sie zwei verschiedene gesperrte Objekte haben, können Sie zwei Threads haben, die dasselbe Objekt gleichzeitig ändern.

Slimu
quelle
14

Kann jemand erklären, warum die obige Klasse nicht threadsicher ist?

  • increment Da es statisch ist, wird die Synchronisation für die Klasse selbst durchgeführt.
  • decrementDa die Objektinstanziierung nicht statisch ist, wird die Synchronisierung durchgeführt, dies sichert jedoch nichts wie countdie statische.

Ich möchte das hinzufügen, um einen thread-sicheren Zähler zu deklarieren. Ich glaube, der einfachste Weg ist, AtomicIntegeranstelle eines primitiven int zu verwenden.

Lassen Sie sich von mir zu den java.util.concurrent.atomicPaketinformationen weiterleiten.

Jean-François Savard
quelle
7

Die Antworten anderer sind ziemlich gut, erklärte der Grund. Ich füge nur etwas hinzu, um es zusammenzufassen 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();

synchronizedon fun1und fun2wird auf Instanzobjektebene synchronisiert. synchronizedon fun4wird auf Klassenobjektebene synchronisiert. Was bedeutet:

  1. Wenn 2 Threads a1.fun1()gleichzeitig anrufen, wird der letztere Anruf blockiert.
  2. Wenn Thread 1 a1.fun1()und Thread 2 a1.fun2()gleichzeitig aufrufen, wird der letztere Anruf blockiert.
  3. Wenn Thread 1 a1.fun1()und Thread 2 gleichzeitig aufrufen a1.fun3(), ohne zu blockieren, werden die beiden Methoden gleichzeitig ausgeführt.
  4. Wenn Thread 1 aufruft A.fun4(), wenn andere Threads aufrufen A.fun4()oder A.fun5()gleichzeitig, werden letztere Aufrufe blockiert, da synchronizedon auf fun4Klassenebene ist.
  5. Wenn Thread 1 aufruft A.fun4(), Thread 2 a1.fun1()gleichzeitig aufruft, keine Blockierung, werden die beiden Methoden gleichzeitig ausgeführt.
coderz
quelle
6
  1. decrementist auf eine andere Sache zu sperren, incrementdamit sie sich nicht gegenseitig am Laufen hindern.
  2. Das Aufrufen decrementeiner Instanz sperrt eine andere Sache als das Aufrufen einer decrementanderen Instanz, wirkt sich jedoch auf dasselbe aus.

Das erste bedeutet, dass überlappende Anrufe zu incrementund decrementzu einer Löschung (korrekt), einem Inkrement oder einem Dekrement führen können.

Das zweite bedeutet, dass zwei überlappende Aufrufe decrementauf verschiedenen Instanzen zu einer doppelten Dekrementierung (korrekt) oder einer einzelnen Dekrementierung führen können.

Jon Hanna
quelle
4

Da zwei verschiedene Methoden eine Instanzebene und eine Klassenebene sind, müssen Sie zwei verschiedene Objekte sperren, um ThreadSafe zu erstellen

jaleel_quest
quelle
1

Wie in anderen Antworten erläutert, ist Ihr Code nicht threadsicher, da die statische Methode den increment()Klassenmonitor und die nicht statische Methode den decrement()Objektmonitor sperrt.

Für dieses Codebeispiel gibt es eine bessere Lösung ohne synchronzedVerwendung von Schlüsselwörtern. Sie müssen AtomicInteger verwenden , um die Thread-Sicherheit zu erreichen.

Thread sicher mit 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
quelle