Unterschied zwischen flüchtig und synchronisiert in Java

233

Ich wundere mich über den Unterschied zwischen dem Deklarieren einer Variablen als volatileund dem Zugriff auf die Variable in einem synchronized(this)Block in Java.

Laut diesem Artikel http://www.javamex.com/tutorials/synchronization_volatile.shtml gibt es viel zu sagen und es gibt viele Unterschiede, aber auch einige Ähnlichkeiten.

Ich interessiere mich besonders für diese Information:

...

  • Der Zugriff auf eine flüchtige Variable hat niemals das Potenzial zu blockieren: Wir führen immer nur ein einfaches Lesen oder Schreiben durch, sodass wir im Gegensatz zu einem synchronisierten Block niemals an einer Sperre festhalten werden.
  • Da der Zugriff auf eine flüchtige Variable niemals eine Sperre enthält, ist sie nicht für Fälle geeignet, in denen wir als atomare Operation lesen, aktualisieren und schreiben möchten (es sei denn, wir sind bereit, "ein Update zu verpassen").

Was verstehen sie unter Lesen-Aktualisieren-Schreiben ? Ist ein Schreibvorgang nicht auch ein Update oder bedeutet dies einfach, dass das Update ein Schreibvorgang ist, der vom Lesen abhängt?

Wann ist es vor allem besser, Variablen zu deklarieren, volatileals über einen synchronizedBlock darauf zuzugreifen ? Ist es eine gute Idee, volatileVariablen zu verwenden, die von der Eingabe abhängen? Zum Beispiel gibt es eine Variable namens render, die durch die Rendering-Schleife gelesen und durch ein Tastendruckereignis festgelegt wird.

Albus Dumbledore
quelle

Antworten:

383

Es ist wichtig zu verstehen, dass die Thread-Sicherheit zwei Aspekte hat.

  1. Ausführungskontrolle und
  2. Speichersichtbarkeit

Das erste hat damit zu tun, zu steuern, wann Code ausgeführt wird (einschließlich der Reihenfolge, in der Anweisungen ausgeführt werden) und ob er gleichzeitig ausgeführt werden kann, und das zweite damit, wann die Auswirkungen im Speicher dessen, was getan wurde, für andere Threads sichtbar sind. Da jede CPU mehrere Cache-Ebenen zwischen sich und dem Hauptspeicher hat, können Threads, die auf verschiedenen CPUs oder Kernen ausgeführt werden, "Speicher" zu einem bestimmten Zeitpunkt unterschiedlich sehen, da Threads private Kopien des Hauptspeichers abrufen und bearbeiten dürfen.

Durch synchronizeddie Verwendung wird verhindert, dass ein anderer Thread den Monitor (oder die Sperre) für dasselbe Objekt erhält , wodurch verhindert wird, dass alle durch die Synchronisation für dasselbe Objekt geschützten Codeblöcke gleichzeitig ausgeführt werden. Durch die Synchronisierung wird auch eine "Barriere vor" -Speicherbarriere erstellt, die eine Einschränkung der Speichersichtbarkeit verursacht, sodass alles, was bis zu dem Punkt getan wird, an dem ein Thread eine Sperre aufhebt , einem anderen Thread angezeigt wird , der anschließend dieselbe Sperre erhält , die vor dem Erwerb der Sperre aufgetreten ist. In der Praxis führt dies bei aktueller Hardware normalerweise zum Leeren der CPU-Caches, wenn ein Monitor erfasst wird, und zum Schreiben in den Hauptspeicher, wenn er freigegeben wird. Beide sind (relativ) teuer.

Die Verwendung volatileerzwingt andererseits, dass alle Zugriffe (Lesen oder Schreiben) auf die flüchtige Variable auf den Hauptspeicher erfolgen, wodurch die flüchtige Variable effektiv aus den CPU-Caches herausgehalten wird. Dies kann für einige Aktionen nützlich sein, bei denen lediglich die Sichtbarkeit der Variablen korrekt sein muss und die Reihenfolge der Zugriffe nicht wichtig ist. Die Verwendung volatileändert auch die Behandlung von longund doubleerfordert, dass Zugriffe auf sie atomar sind; Bei einigen (älteren) Hardwarekomponenten sind möglicherweise Sperren erforderlich, bei moderner 64-Bit-Hardware jedoch nicht. Unter dem neuen Speichermodell (JSR-133) für Java 5+ wurde die Semantik von flüchtigen Verbindungen so stark wie synchronisiert in Bezug auf Speichersichtbarkeit und Befehlsreihenfolge gestärkt (siehe http://www.cs.umd.edu) /users/pugh/java/memoryModel/jsr-133-faq.html#volatile). Aus Gründen der Sichtbarkeit wirkt jeder Zugriff auf ein flüchtiges Feld wie eine halbe Synchronisation.

Unter dem neuen Speichermodell ist es immer noch richtig, dass flüchtige Variablen nicht miteinander neu angeordnet werden können. Der Unterschied besteht darin, dass es jetzt nicht mehr so ​​einfach ist, normale Feldzugriffe um sie herum neu zu ordnen. Das Schreiben in ein flüchtiges Feld hat den gleichen Speichereffekt wie eine Monitorfreigabe, und das Lesen aus einem flüchtigen Feld hat den gleichen Speichereffekt wie ein Monitorerwerb. Da das neue Speichermodell die Neuordnung flüchtiger Feldzugriffe mit anderen Feldzugriffen, ob flüchtig oder nicht, strenger einschränkt, wird alles, was Abeim Schreiben in ein flüchtiges Feld ffür den Thread sichtbar war, Bbeim Lesen für den Thread sichtbar f.

- Häufig gestellte Fragen zu JSR 133 (Java Memory Model)

Daher verursachen beide Formen der Speicherbarriere (unter dem aktuellen JMM) eine Barriere für die Neuordnung von Befehlen, die verhindert, dass der Compiler oder die Laufzeit Befehle über die Barriere hinweg neu anordnen. Im alten JMM verhinderte flüchtig nicht die Nachbestellung. Dies kann wichtig sein, da abgesehen von Speicherbarrieren die einzige Einschränkung darin besteht, dass für einen bestimmten Thread der Nettoeffekt des Codes der gleiche ist, als wenn die Anweisungen genau in der Reihenfolge ausgeführt würden, in der sie in der erscheinen Quelle.

Eine Verwendung von flüchtig ist, dass ein gemeinsam genutztes, aber unveränderliches Objekt im laufenden Betrieb neu erstellt wird, wobei viele andere Threads zu einem bestimmten Zeitpunkt in ihrem Ausführungszyklus auf das Objekt verweisen. Die anderen Threads müssen das neu erstellte Objekt verwenden, sobald es veröffentlicht ist, benötigen jedoch nicht den zusätzlichen Aufwand für die vollständige Synchronisierung und die damit verbundenen Konflikte und das Leeren des Caches.

// Declaration
public class SharedLocation {
    static public SomeObject someObject=new SomeObject(); // default object
    }

// Publishing code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
//       someObject will be internally consistent for xxx(), a subsequent 
//       call to yyy() might be inconsistent with xxx() if the object was 
//       replaced in between calls.
SharedLocation.someObject=new SomeObject(...); // new object is published

// Using code
private String getError() {
    SomeObject myCopy=SharedLocation.someObject; // gets current copy
    ...
    int cod=myCopy.getErrorCode();
    String txt=myCopy.getErrorText();
    return (cod+" - "+txt);
    }
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.

Sprechen Sie speziell mit Ihrer Frage zum Lesen, Aktualisieren und Schreiben. Betrachten Sie den folgenden unsicheren Code:

public void updateCounter() {
    if(counter==1000) { counter=0; }
    else              { counter++; }
    }

Wenn die updateCounter () -Methode nicht synchronisiert ist, können zwei Threads gleichzeitig darauf zugreifen. Unter den vielen Permutationen dessen, was passieren könnte, ist eine, dass Thread-1 den Test für den Zähler == 1000 durchführt und ihn für wahr hält und dann ausgesetzt wird. Dann führt Thread-2 den gleichen Test durch und sieht ihn auch als wahr an und wird angehalten. Dann wird Thread-1 fortgesetzt und der Zähler auf 0 gesetzt. Dann wird Thread-2 fortgesetzt und setzt den Zähler erneut auf 0, da das Update von Thread-1 verpasst wurde. Dies kann auch passieren, wenn der Thread-Wechsel nicht wie beschrieben erfolgt, sondern einfach, weil zwei verschiedene zwischengespeicherte Kopien des Zählers in zwei verschiedenen CPU-Kernen vorhanden waren und die Threads jeweils auf einem separaten Kern liefen. In diesem Fall könnte ein Thread einen Zähler bei einem Wert und der andere einen Zähler bei einem völlig anderen Wert haben, nur weil er zwischengespeichert ist.

Was in diesem Beispiel wichtig ist , dass die Variable Zähler wurde aus dem Hauptspeicher in den Cache gelesen, im Cache aktualisiert und erst später in den Hauptspeicher an einem gewissen unbestimmten Punkt zurückgeschrieben , wenn eine Speicherbarriere aufgetreten ist oder wenn der Cache - Speicher wurde für etwas anderes benötigt. Das Erstellen des Zählers reicht volatilefür die Thread-Sicherheit dieses Codes nicht aus, da der Test für das Maximum und die Zuweisungen diskrete Operationen sind, einschließlich des Inkrements, bei dem es sich um einen Satz nichtatomarer read+increment+writeMaschinenanweisungen handelt, wie z.

MOV EAX,counter
INC EAX
MOV counter,EAX

Flüchtige Variablen sind nur dann nützlich, wenn alle an ihnen ausgeführten Operationen "atomar" sind, wie in meinem Beispiel, in dem ein Verweis auf ein vollständig geformtes Objekt nur gelesen oder geschrieben wird (und in der Tat normalerweise nur von einem einzelnen Punkt aus geschrieben wird). Ein anderes Beispiel wäre eine flüchtige Array-Referenz, die eine Copy-on-Write-Liste unterstützt, vorausgesetzt, das Array wurde nur gelesen, indem zuerst eine lokale Kopie der Referenz darauf erstellt wurde.

Lawrence Dol
quelle
5
Vielen Dank! Das Beispiel mit dem Zähler ist einfach zu verstehen. Wenn die Dinge jedoch real werden, ist es ein bisschen anders.
Albus Dumbledore
"In der Praxis führt dies bei aktueller Hardware in der Regel dazu, dass die CPU-Caches beim Erwerb eines Monitors geleert werden und beim Freigeben in den Hauptspeicher geschrieben werden. Beides ist teuer (relativ gesehen)." . Wenn Sie CPU-Caches sagen, ist es dasselbe wie Java-Stacks, die für jeden Thread lokal sind? oder hat ein Thread eine eigene lokale Version von Heap? Entschuldige mich, wenn ich hier albern bin.
NishM
1
@nishm Es ist nicht dasselbe, aber es würde die lokalen Caches der beteiligten Threads enthalten. .
Lawrence Dol
1
@ MarianPaździoch: Ein Inkrement oder Dekrement ist KEIN Lesen oder Schreiben, es ist ein Lesen und ein Schreiben; Es ist ein Einlesen in ein Register, dann ein Registerinkrement und dann ein Zurückschreiben in den Speicher. Lese- und Schreibvorgänge sind einzeln atomar, mehrere solcher Operationen jedoch nicht.
Lawrence Dol
2
Gemäß den häufig gestellten Fragen werden also nicht nur die Aktionen, die seit einer Sperrenerfassung ausgeführt wurden, nach dem Entsperren sichtbar gemacht, sondern alle von diesem Thread ausgeführten Aktionen werden sichtbar gemacht. Sogar Aktionen, die vor dem Erwerb der Sperre ausgeführt wurden.
Lii
97

flüchtig ist ein Feldmodifikator , während synchronisiert Codeblöcke und -methoden modifiziert . Mit diesen beiden Schlüsselwörtern können wir also drei Varianten eines einfachen Accessors angeben:

    int i1;
    int geti1() {return i1;}

    volatile int i2;
    int geti2() {return i2;}

    int i3;
    synchronized int geti3() {return i3;}

geti1()greift auf den aktuell i1im aktuellen Thread gespeicherten Wert zu . Threads können lokale Kopien von Variablen haben, und die Daten müssen nicht mit den Daten in anderen Threads identisch sein. Insbesondere wurde möglicherweise ein anderer Thread i1in seinem Thread aktualisiert , aber der Wert im aktuellen Thread kann davon abweichen aktualisierter Wert. Tatsächlich hat Java die Idee eines "Hauptspeichers", und dieser Speicher enthält den aktuellen "richtigen" Wert für Variablen. Threads können eine eigene Kopie der Daten für Variablen haben, und die Thread-Kopie kann sich vom "Hauptspeicher" unterscheiden. Tatsächlich ist es also möglich, dass der "Hauptspeicher" einen Wert von 1 für i1, für Thread1 einen Wert von 2 für i1und für Thread2 hateinen Wert von 3 zu haben, i1wenn sowohl Thread1 als auch Thread2 i1 aktualisiert haben, dieser aktualisierte Wert jedoch noch nicht an den "Hauptspeicher" oder andere Threads weitergegeben wurde.

Auf der anderen Seite wird geti2()effektiv auf den Wert i2von "Hauptspeicher" zugegriffen. Eine flüchtige Variable darf keine lokale Kopie einer Variablen haben, die sich von dem Wert unterscheidet, der derzeit im "Hauptspeicher" gespeichert ist. Tatsächlich müssen die Daten einer als flüchtig deklarierten Variablen über alle Threads hinweg synchronisiert sein, damit alle anderen Threads sofort denselben Wert sehen, wenn Sie auf die Variable in einem Thread zugreifen oder diese aktualisieren. Im Allgemeinen haben flüchtige Variablen einen höheren Zugriffs- und Aktualisierungsaufwand als "einfache" Variablen. Im Allgemeinen dürfen Threads eine eigene Kopie der Daten haben, um die Effizienz zu verbessern.

Es gibt zwei Unterschiede zwischen flüchtig und synchronisiert.

Zunächst werden durch die Synchronisierung Sperren für Monitore abgerufen und freigegeben, die jeweils nur einen Thread zur Ausführung eines Codeblocks zwingen können. Das ist der ziemlich bekannte Aspekt der Synchronisation. Synchronisiert synchronisiert aber auch den Speicher. Tatsächlich synchronisiert synchronized den gesamten Thread-Speicher mit dem "Hauptspeicher". Die Ausführung geti3()führt also Folgendes aus:

  1. Der Thread erhält die Sperre auf dem Monitor für Objekt dies.
  2. Der Thread-Speicher löscht alle seine Variablen, dh er lässt alle seine Variablen effektiv aus dem "Hauptspeicher" lesen.
  3. Der Codeblock wird ausgeführt (in diesem Fall wird der Rückgabewert auf den aktuellen Wert von i3 gesetzt, der möglicherweise gerade aus dem "Hauptspeicher" zurückgesetzt wurde).
  4. (Änderungen an Variablen werden normalerweise in den "Hauptspeicher" geschrieben, aber für geti3 () haben wir keine Änderungen.)
  5. Der Thread gibt die Sperre auf dem Monitor für dieses Objekt frei.

Wenn also flüchtig nur den Wert einer Variablen zwischen Thread-Speicher und "Hauptspeicher" synchronisiert, synchronisiert synchronisiert den Wert aller Variablen zwischen Thread-Speicher und "Hauptspeicher" und sperrt und gibt einen Monitor zum Booten frei. Klar synchronisiert ist wahrscheinlich mehr Overhead als volatil.

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html

Kerem Baydoğan
quelle
35
-1, Volatile erhält keine Sperre, sondern verwendet die zugrunde liegende CPU-Architektur, um die Sichtbarkeit aller Threads nach dem Schreiben sicherzustellen.
Michael Barker
Es ist erwähnenswert, dass es einige Fälle geben kann, in denen eine Sperre verwendet werden kann, um die Atomizität von Schreibvorgängen zu gewährleisten. Schreiben Sie beispielsweise ein Long auf einer 32-Bit-Plattform, die keine erweiterten Breitenrechte unterstützt. Intel vermeidet dies, indem SSE2-Register (128 Bit breit) verwendet werden, um flüchtige Longs zu verarbeiten. Wenn Sie jedoch ein flüchtiges Element als Sperre betrachten, führt dies wahrscheinlich zu bösen Fehlern in Ihrem Code.
Michael Barker
2
Die wichtige Semantik, die Sperren und flüchtige Variablen gemeinsam haben, besteht darin, dass beide Happens-Before-Kanten bereitstellen (Java 1.5 und höher). Das Betreten eines synchronisierten Blocks, das Herausnehmen einer Sperre und das Lesen von einem flüchtigen Block werden alle als "Erfassen" betrachtet, und das Aufheben einer Sperre, das Verlassen eines synchronisierten Blocks und das Schreiben eines flüchtigen Blocks sind alle Formen einer "Freigabe".
Michael Barker
20

synchronizedist der Zugriffsbeschränkungsmodifikator auf Methoden- / Blockebene. Dadurch wird sichergestellt, dass ein Thread die Sperre für kritische Abschnitte besitzt. Nur der Thread, der eine Sperre besitzt, kann in den synchronizedBlock eintreten . Wenn andere Threads versuchen, auf diesen kritischen Abschnitt zuzugreifen, müssen sie warten, bis der aktuelle Eigentümer die Sperre aufhebt.

volatileist ein Modifikator für den variablen Zugriff, der alle Threads dazu zwingt, den neuesten Wert der Variablen aus dem Hauptspeicher abzurufen. Für den Zugriff auf volatileVariablen ist keine Sperrung erforderlich . Alle Threads können gleichzeitig auf den Wert der flüchtigen Variablen zugreifen.

Ein gutes Beispiel für die Verwendung einer flüchtigen Variablen: DateVariable.

Angenommen, Sie haben die Datumsvariable festgelegt volatile. Alle Threads, die auf diese Variable zugreifen, erhalten immer die neuesten Daten aus dem Hauptspeicher, sodass alle Threads den tatsächlichen (tatsächlichen) Datumswert anzeigen. Sie benötigen keine unterschiedlichen Threads, die unterschiedliche Zeiten für dieselbe Variable anzeigen. Alle Threads sollten den richtigen Datumswert anzeigen.

Geben Sie hier die Bildbeschreibung ein

Schauen Sie sich diesen Artikel an, um das volatileKonzept besser zu verstehen .

Lawrence Dol Cleary erklärte Ihre read-write-update query.

In Bezug auf Ihre anderen Fragen

Wann ist es besser, Variablen als flüchtig zu deklarieren, als synchronisiert darauf zuzugreifen?

Sie müssen verwenden, volatilewenn Sie der Meinung sind, dass alle Threads den tatsächlichen Wert der Variablen in Echtzeit erhalten sollen, wie in dem Beispiel, das ich für die Datumsvariable erläutert habe.

Ist es eine gute Idee, volatile für Variablen zu verwenden, die von der Eingabe abhängen?

Die Antwort ist dieselbe wie bei der ersten Abfrage.

Weitere Informationen finden Sie in diesem Artikel .

Ravindra Babu
quelle
Das Lesen kann also gleichzeitig erfolgen, und alle Threads lesen den neuesten Wert, da die CPU den Hauptspeicher nicht im CPU-Thread-Cache zwischenspeichert, aber was ist mit dem Schreiben? Schreiben darf nicht gleichzeitig korrekt sein? Zweite Frage: Wenn ein Block synchronisiert ist, die Variable jedoch nicht flüchtig ist, kann der Wert einer Variablen in einem synchronisierten Block immer noch von einem anderen Thread in einem anderen Codeblock geändert werden, oder?
the_prole
11

tl; dr :

Beim Multithreading gibt es drei Hauptprobleme:

1) Rennbedingungen

2) Caching / veralteter Speicher

3) Complier- und CPU-Optimierungen

volatilekann 2 & 3 lösen, aber nicht 1. synchronized/ Explizite Sperren können 1, 2 & 3 lösen.

Ausarbeitung :

1) Betrachten Sie diesen Thread als unsicheren Code:

x++;

Während es wie eine Operation aussehen mag, ist es tatsächlich 3: Lesen des aktuellen Werts von x aus dem Speicher, Hinzufügen von 1 und Speichern im Speicher. Wenn nur wenige Threads gleichzeitig versuchen, dies zu tun, ist das Ergebnis der Operation undefiniert. Wenn xursprünglich 1 war, kann es nach 2 Threads, die den Code ausführen, 2 und 3 sein, abhängig davon, welcher Thread welchen Teil der Operation abgeschlossen hat, bevor die Steuerung auf den anderen Thread übertragen wurde. Dies ist eine Form der Rennbedingung .

Die Verwendung synchronizedeines Codeblocks macht ihn atomar - was bedeutet, dass es so aussieht, als würden die drei Operationen gleichzeitig ausgeführt, und es gibt keine Möglichkeit, dass ein anderer Thread in die Mitte kommt und sich einmischt. Wenn also x1 und 2 Threads versuchen, sich vorzubereiten x++, wissen wir, dass es am Ende gleich 3 sein wird. Damit wird das Problem der Rennbedingungen gelöst.

synchronized (this) {
   x++; // no problem now
}

Das Markieren xals volatilemacht nicht x++;atomar, daher wird dieses Problem nicht gelöst.

2) Außerdem haben Threads einen eigenen Kontext, dh sie können Werte aus dem Hauptspeicher zwischenspeichern. Das bedeutet, dass einige Threads Kopien einer Variablen haben können, aber sie arbeiten mit ihrer Arbeitskopie, ohne den neuen Status der Variablen mit anderen Threads zu teilen.

Betrachten Sie das auf einem Thread , x = 10;. Und etwas später in einem anderen Thread x = 20;. Die Wertänderung von wird xmöglicherweise nicht im ersten Thread angezeigt, da der andere Thread den neuen Wert in seinem Arbeitsspeicher gespeichert, ihn jedoch nicht in den Hauptspeicher kopiert hat. Oder dass es es in den Hauptspeicher kopiert hat, aber der erste Thread seine Arbeitskopie nicht aktualisiert hat. Wenn nun der erste Thread prüft, lautet if (x == 20)die Antwort false.

Das Markieren einer Variablen als volatileweist im Grunde alle Threads an, Lese- und Schreibvorgänge nur im Hauptspeicher auszuführen. synchronizedWeist jeden Thread an, seinen Wert aus dem Hauptspeicher zu aktualisieren, wenn er den Block betritt, und das Ergebnis beim Verlassen des Blocks in den Hauptspeicher zurückzuspülen.

Beachten Sie, dass veralteter Speicher im Gegensatz zu Datenrassen nicht so einfach (neu) zu erstellen ist, da ohnehin Leergut in den Hauptspeicher erfolgt.

3) Der Complier und die CPU können (ohne jegliche Form der Synchronisation zwischen Threads) den gesamten Code als Single-Threading behandeln. Das heißt, es kann sich einen Code ansehen, der in einem Multithreading-Aspekt sehr bedeutsam ist, und ihn so behandeln, als wäre er Single-Threaded, wo er nicht so aussagekräftig ist. Es kann sich also einen Code ansehen und aus Gründen der Optimierung entscheiden, ihn neu zu ordnen oder sogar Teile davon vollständig zu entfernen, wenn es nicht weiß, dass dieser Code für die Verwendung mit mehreren Threads ausgelegt ist.

Betrachten Sie den folgenden Code:

boolean b = false;
int x = 10;

void threadA() {
    x = 20;
    b = true;
}

void threadB() {
    if (b) {
        System.out.println(x);
    }
}

Sie würden denken, dass threadB nur 20 drucken könnte (oder überhaupt nichts drucken könnte, wenn threadB if-check ausgeführt wird, bevor bauf true gesetzt wird), da btrue erst gesetzt wird, nachdem xauf 20 gesetzt wurde, aber der Compiler / die CPU könnte sich für eine Neuordnung entscheiden threadA, in diesem Fall könnte threadB auch 10 drucken. Durch Markieren bals wird volatilesichergestellt, dass es nicht neu angeordnet (oder in bestimmten Fällen verworfen) wird. Was bedeutet, dass threadB nur 20 (oder gar nichts) drucken konnte. Durch Markieren der Methoden als synchronisiert wird das gleiche Ergebnis erzielt. Durch das Markieren einer Variablen als wird volatilenur sichergestellt, dass sie nicht neu angeordnet wird, aber alles davor / danach kann noch neu angeordnet werden, sodass die Synchronisierung in einigen Szenarien besser geeignet ist.

Beachten Sie, dass volatile dieses Problem vor Java 5 New Memory Model nicht gelöst hat.

David Refaeli
quelle
1
"Während es wie eine Operation aussieht, ist es tatsächlich 3: Lesen des aktuellen Werts von x aus dem Speicher, Hinzufügen von 1 und Speichern im Speicher." - Richtig, weil Werte aus dem Speicher die CPU-Schaltung durchlaufen müssen, um hinzugefügt / geändert zu werden. Obwohl dies nur zu einer einzelnen Assembly- INCOperation wird, sind die zugrunde liegenden CPU-Operationen immer noch dreifach und erfordern aus Gründen der Thread-Sicherheit eine Sperre. Guter Punkt. Obwohl die INC/DECBefehle in der Assembly atomar markiert werden können und immer noch eine atomare Operation sind.
Zombies
@Zombies Wenn ich also einen synchronisierten Block für x ++ erstelle, verwandelt er sich dann in ein gekennzeichnetes atomares INC / DEC oder verwendet er eine reguläre Sperre?
David Refaeli
Ich weiß es nicht! Was ich weiß ist, dass INC / DEC nicht atomar sind, weil sie für eine CPU den Wert laden und LESEN und ihn auch (in den Speicher) schreiben müssen, genau wie jede andere arithmetische Operation.
Zombies