Klausurlösung WS 13(/14): 18.02.2014


Ja, die CyclicBarrier garantiert Sichtbarkeitssynchronisation. Das Problem liegt aber woanders. Schau dir nochmal ganz genau den Code an. :wink:


Danke für die Antwort)
counter ist ein „normales“ int. Aber ich dachte, z.B. ein Thread erhöht counter um eins, wartet, und danach ein zweites Thread erhöht counter um eins. Dann hat der counter wert 2 und wir haben „Ultimate Question“ als Ausgabe… counter wird noch niergendwo mehr geändert… Geht es hier genau um diese Abfrage (Z.71-72), dass sie nicht „atomar“ erfolgt? [bin total verzweifelt…]
Ich kann naturlich „sicherheitshalber“ sagen, dass statt int counter mache ich AtomicInteger, das löst aber mein Verständnisproblem nicht :frowning:


Das Erhöhen von [m]counter[/m] mittels [m]counter++[/m] ist keine atomare Aktion, sondern setzt sich zusammen aus dem Laden des alten Werts, dem Addieren von 1 auf diesen alten Wert und dem Speichern des neuen Werts in der Variable counter. Zwischen all diesen Operationen kann es nun zu Wettlaufsituationen kommen und es kann passieren, dass beide Threads zuerst den alten Wert lesen (0) beide diesen alten Wert erhöhen (1) und diesen dann zurückschreiben (wobei ein Thread den anderen überschreibt). Das Ergebnis ist dann 1 statt 2. Daran ändert dann auch die Sichtbarkeitssynchronisation durch die Barrier nichts mehr, da die Wettlaufsituation vorher auftritt. Durch nutzen eines AtomicInteger oder Verwendung von synchronized kann das Problem entsprechend lösen.


Danke für sehr ausführliche und verständliche Antwort! Diese Erklärung hat mir geholfen, mein tatsächliches Verständnisproblem „sicher“ feststellen :slight_smile:

Das ist mir bekannt. Wie ich vermutet habe, verstehe ich das Konzept des CyclicBarriers einfach nicht komplett. Der CyclicBarrier garantiert [nur(?)] das es auf alle Threads gewartet wird, dass sie alle ihre Arbeit erledigen, bevor es weitergeht. Und das ist einzigste, was er kann? (oder kann er mehr? ich habe mir immer gedacht, dass er noch „mächtiger“ ist :slight_smile: alle Beispiele, die ich im Internet gefunden habe, haben mein Problem leider nicht geklärt…)
Es gibt aber solche Fälle, wo das hier

int counter = 0;

barrier.await();
counter++;
barrier.await();

doch richtig funktionieren wird, obwohl counter „nur“ ein int ist, oder? Und wenn ja, es wäre super ein Beispiel dafür zu finden :slight_smile:


Die CyclicBarrier garantiert Sichtbarkeitssynchronisation, das bedeutet, dass die Schreibzugriffe, die vor dem [m]await()[/m]-Aufruf in den beteiligten Threads geschehen, danach für die beteiligten Threads sichtbar sind. Das Problem mit [m]counter++[/m], welches CodeMonkey geschildert hat, ist aber kein (oder genauer: nicht nur ein) Sichtbarkeitsproblem, sondern (auch) eine Wettlaufsituation.

Das funktioniert, wenn zum Beispiel nur ein Thread [m]counter++[/m] durchführt und ein anderer nach dem zweiten [m]barrier.await()[/m] lesend auf [m]counter[/m] zugreift. Ohne die CyclicBarrier würde es dann zu nichtdeterministischem Verhalten kommen.


Am Anfang dachte ich, dass CyclicBarrier auch fuer mehrere Threads Sichtbarkeitssynchronisation garantiert.
vielen Dank! Die Erklaerungen haben mir sehr geholfen :slight_smile:


Ich glaube du verstehst Sichtbarkeitssynchronisation in diesem Fall einfach falsch. Denn eine CyclicBarrier kümmert sich durchaus um Sichtbarkeitssynchronisation unter mehreren Threads. Allerdings tritt hier kein Sichtbarkeitsproblem nach der Barrier auf.

Ausführlicher geschrieben steht da in etwa (der restliche Code ist für das eigentliche Problem irrelevant) :

barrier.await();
int tmp = counter;
int tmp2 = tmp + 1;
counter = tmp2;
barrier.await();

Die Barrier sorgt am [m]barrier.await()[/m] dafür, dass Sichtbarkeitssynchronisation durchgeführt wird. D.h. nach dem [m]barrier.await()[/m] ist der neue Wert von [m]counter[/m] für alle Threads sichtbar. Die Barrier kann aber folgendes nicht verhindern:

  1. Eine Wettlaufsituation, die auftritt, wenn T1 Zeile 2 oder 3 bereits ausgeführt hat, dann aber verdrängt wird und T2 somit [m]tmp = counter[/m] ausführt bevor T1 [m]counter[/m] überhaupt verändert hat. T2 lädt also den alten Wert, weil der neue vom anderen Thread noch nicht einmal berechnet wurde (oder zumindest nicht gespeichert wurde).

  2. Ein Sichtbarkeitsproblem, nachdem T1 Zeile 4 ausgeführt hat. Es kann jetzt passieren, dass T2 Zeile 2 ausführt und obwohl T1 [m]counter[/m] bereits geschrieben hat, wird von T2 noch der alte Wert gelesen (z. B. weil der neue Wert nur bei T1 im Cache liegt oder weil T2 noch einen veralteten Wert im Cache hat, den er nicht aktualisiert). Die Barrier kann hier nicht helfen, weil das Sichtbarkeitsproblem zwischen zwei [m]await[/m]-Aufrufen auftritt.

Die Barrier garantiert Sichtbarkeitssynchronisation über await-Grenzen hinweg. Man kann es sich also so vorstellen, dass an einem await die geänderten Speicheradressen synchronisiert werden. Es wird keine Magie betrieben, um zwischen zwei await-Aufrufen Sichtbarkeitsprobleme zu beheben.

Würde man ein an dieser Stelle ein [m]volatile int[/m] verwenden würde (2.) nicht auftreten können, sondern nur noch (1.). Um (1.) und (2.) auszuschließen und damit Threadsicherheit zu garantieren brauchst du ein synchronized oder ein AtomicInteger.

Ein Beispiel für mehrere Threads, bei denen Sichtbarkeitssynchronisation funktioniert (wie mkmdl es schon angedeutet hat):
T1:

barrier.await();
counter++;
barrier.await();
// don't write to counter

T2:

barrier.await();
// don't use counter
barrier.await();
System.out.println(counter);

In diesem Fall gibt es zum write im ersten Barrieren-Abschnitt keine Entsprechung im anderen Thread und durch die Sichtbarkeitssynchronisation am await wird garantiert, dass [m]counter++[/m] auf jeden Fall vorher durchgeführt wurde und das Ergebnis für T2 sichtbar ist. Würde man die Barriere allerdings weglassen, könnten wieder Wettlaufsituationen und Sichtbarkeitsprobleme auftreten.


Ja, genau diese Vermutung habe ich ganz am Anfang geäußert :slight_smile:

Diese letzte ausführliche Erklärung hat mir bestätigt, dass ich es tatsächlich verstanden habe (und nicht nur im Bezug auf diese Aufgabe :slight_smile: )
Danke CodeMonkey und mkmdl nochmals für gute und ausführliche Erklärungen!

1 „Gefällt mir“