Thread - Methoden synchronized deklarieren

frager2345

Aktives Mitglied
Hi, also ich habe noch nicht ganz verstanden was ganz genau synchronized macht. Also klar ist, dass eine synchronized Methode nur ein Thread ausführen kann , andere Threads müssen warten bis die Methode freigegeben wird. Jedoch versteh ich z.B in diesem Beispiel wenn man erreichen will, dass nur die Ausgabe -9 möglich ist , dass man dann die Methoden synchronized deklariert. Würde es nicht auch einfach reichen die beiden Threads mit join dem main thread vorzuziehen?
Java:
public class IntValue {
    public int value = 0;
    public void inc() {
        value += 1;
    }
    public void dec() {
        value -= 10;
    }
    public void print() {
        System.out.print(value);
    }
}
public class Thread1 extends Thread {
    private IntValue iv;
    public Thread1(IntValue iv) {
        this.iv = iv;
    }
    public void run() {
        iv.inc();
    }
}
public class Thread2 extends Thread {
    private IntValue iv;
    public Thread2(IntValue iv) {
        this.iv = iv;
    }
    public void run() {
        iv.dec();
    }
}
public static void main(String[] args) {
    IntValue v = new IntValue();
    Thread1 t1 = new Thread1(v);
    Thread2 t2 = new Thread2(v); t1.start();
    t2.start();
    v.print();
}
 
1. Das join musst du hier vor v.print(); sowieso machen, um sicherzustellen, dass die Threads fertig werden bevor du den Wert ausgibst.
2. Die Operationen value += 1; und value -= 10; sind nicht atomar. Das heißt, sie bestehen eigentlich aus mehreren Einzeloperationen, die für sich gesehen alle unterbrechbar sind.
Also: i += v; ist:
Code:
int tmp = i;
i = tmp + v;
Die Variable tmp dient hier zur "Operationalisierung" in Form von Java-Code, was tatsächlich passiert. Zwischen den beiden Operationen kann ja schon ein anderer Thread zwischenfunken und i bereits auf einen anderen Wert setzen, als wir in tmp dachten.

Und ein public synchronized void m() {...} ist quasi dasselbe wie public void m() { synchronized (this) { ... } }.
Es wird also auf this synchronisiert, so dass kein anderer Thread gleichzeitig auf this synchronisieren (i.e. "Den Monitor bekommen") kann.
Das kann natürlich über mehrere Codestellen bzw. Methoden sich erstrecken.
 
Ein Problem hier kann tatsächlich sein, dass die Ausgabe nicht wartet, bis die Threads fertig sind. Das wäre synchronized auch nicht zwingend ausreichend, denn es mag ja passieren, dass ein Thread angestartet wurde aber der Aufruf noch nicht gestartet wurde. Dann wäre das print vor dem inc oder dev aufgerufen worden.

Das Synchronized behebt aber ein anderes Problem:

Die Zeile value += 1 ist nicht atomar. Es wird also gemacht:
Der Wert value wird gelesen.
Dann wird diesem gelesenen Wert 1 hinzu addiert.
Das Ergebnis wird in value gelesen.

Beim anderen wäre es ähnlich - nur eben würde 10 abgezogen.

Was also nun bei zwei Threads passieren könnte:
Thread1: value wird gelesen (0)
Thread2: value wird gelesen (0)
Thread1: addiert 1 zur 0 (1)
Thread1: Schreibt die 1 zurück
Thread2 zieht 10 ab (0-10 = -10)
Thread2 schreibt die -10
==> Das Ergebnis -10 hat man nun.
 
1. Das join musst du hier vor v.print(); sowieso machen, um sicherzustellen, dass die Threads fertig werden bevor du den Wert ausgibst.
2. Die Operationen value += 1; und value -= 10; sind nicht atomar. Das heißt, sie bestehen eigentlich aus mehreren Einzeloperationen, die für sich gesehen alle unterbrechbar sind.
Also: i += v; ist:
Code:
int tmp = i;
i = tmp + v;
Die Variable tmp dient hier zur "Operationalisierung" in Form von Java-Code, was tatsächlich passiert. Zwischen den beiden Operationen kann ja schon ein anderer Thread zwischenfunken und i bereits auf einen anderen Wert setzen, als wir in tmp dachten.

Und ein public synchronized void m() {...} ist quasi dasselbe wie public void m() { synchronized (this) { ... } }.
Es wird also auf this synchronisiert, so dass kein anderer Thread gleichzeitig auf this synchronisieren (i.e. "Den Monitor bekommen") kann.
Das kann natürlich über mehrere Codestellen bzw. Methoden sich erstrecken.
Ah ok, also liegt es daran dass dann die einzelnen Befhele wieder aus vielen Maschinenbefehlen bestehen und diese sich wieder untereinander verzahnen können?
 
Ein Problem hier kann tatsächlich sein, dass die Ausgabe nicht wartet, bis die Threads fertig sind. Das wäre synchronized auch nicht zwingend ausreichend, denn es mag ja passieren, dass ein Thread angestartet wurde aber der Aufruf noch nicht gestartet wurde. Dann wäre das print vor dem inc oder dev aufgerufen worden.

Das Synchronized behebt aber ein anderes Problem:

Die Zeile value += 1 ist nicht atomar. Es wird also gemacht:
Der Wert value wird gelesen.
Dann wird diesem gelesenen Wert 1 hinzu addiert.
Das Ergebnis wird in value gelesen.

Beim anderen wäre es ähnlich - nur eben würde 10 abgezogen.

Was also nun bei zwei Threads passieren könnte:
Thread1: value wird gelesen (0)
Thread2: value wird gelesen (0)
Thread1: addiert 1 zur 0 (1)
Thread1: Schreibt die 1 zurück
Thread2 zieht 10 ab (0-10 = -10)
Thread2 schreibt die -10
==> Das Ergebnis -10 hat man nun.
Achse ok, ja dann würde join nicht reichen. Danke dir!
 
Ah ok, also liegt es daran dass dann die einzelnen Befhele wieder aus vielen Maschinenbefehlen bestehen und diese sich wieder untereinander verzahnen können?
Innerhalb solch eines niedrigen Abstraktionslevels würde ich das jetzt nicht beschreiben, aber grundsätzlich: ja.
Selbst bei Maschinenbefehlen ist es nicht (immer) so, dass ein einzelner z.B. x86 Befehl/Opcode atomar ist, da er wiederum aus mehreren µ-Operations in der CPU zusammengesetzt sein kann, die wiederum separat/einzeln gescheduled werden und unterbrochen werden können. Beispiel bei x86 ist INC <reg/mem>. (ja, es sei denn, du nutzt den lock Prefix bei x86).
 
Innerhalb solch eines niedrigen Abstraktionslevels würde ich das jetzt nicht beschreiben, aber grundsätzlich: ja.
Selbst bei Maschinenbefehlen ist es nicht (immer) so, dass ein einzelner z.B. x86 Befehl/Opcode atomar ist, da er wiederum aus mehreren µ-Operations in der CPU zusammengesetzt sein kann, die wiederum separat/einzeln gescheduled werden und unterbrochen werden können. Beispiel bei x86 ist INC <reg/mem>. (ja, es sei denn, du nutzt den lock Prefix bei x86).
Ok alles klar, danke für die Antworten!
 

Zurück
Oben