1. Synchronisationsprobleme vorschlagen
Angenommen, wir verwenden einen Dual-Core-Prozessor, um zwei Threads A und B, Core 1 auszuführen, Thread A, und Core 2 führt Thread B aus. Beide Threads müssen nun der Mitgliedsvariable I des Objekts namens OBJ hinzufügen. Unter der Annahme, dass der Anfangswert von I 0 theoretisch den Wert von I 2 beträgt, sollte ich 2 nach dem Laufen der beiden Threads werden, aber tatsächlich ist es sehr wahrscheinlich, dass das Ergebnis 1 sein wird.
Lassen Sie uns jetzt die Gründe analysieren. Aus Einfachheit der Analyse berücksichtigen wir die Cache -Situation nicht. Tatsächlich gibt es einen Cache, der die Möglichkeit erhöht, dass das Ergebnis 1. Thread A die Variable I im Speicher in die arithmetische Einheit von Kernel 1 beträgt, dann den Additionsvorgang durchführt und dann das Berechnungsergebnis wieder in den Speicher schreibt. Da es sich bei der obigen Operation um keine Atomoperation handelt, wird der Wert von I den Wert von I im Speicher des Gedächtnisses vorliegt, indem der Wert von i zu diesem Zeitpunkt 1 zurückgeführt wird (der Wert von I ist 0), dann wird das Ergebnis von Ich definitiv erscheinen. Wenn der Wert von I gelesen wird, gelesen nach Threads A und B ist 0, und der Wert nach dem Wert.
Die häufigste Lösung besteht darin, das Schlüsselwort Synchronize zu verwenden, um das OBJ-Objekt mit dem Code zu sperren, der dem i-sichtbaren Code in zwei Threads 1 hinzufügt. Heute stellen wir eine neue Lösung vor, die zu verwandten Klassen im Atompaket verwendet wird, um sie zu lösen.
2.Atomische Hardwareunterstützung
In einem einzigen Prozessorsystem (Uniprocessor) können Vorgänge, die in einer einzigen Anweisung ausgeführt werden können, als "Atomoperationen" betrachtet werden, da Interrupts nur zwischen den Anweisungen auftreten können (da die Thread -Planung durch Interrupts abgeschlossen werden muss). Dies ist auch der Grund, warum einige CPU -Anweisungssysteme Test_and_set, test_and_clear und andere Anweisungen für den gegenseitigen Ausschluss kritischer Ressourcen einführen. Es ist in der symmetrischen Multi-Proprozessor-Struktur unterschiedlich, da mehrere Prozessoren unabhängig im System ausgeführt werden, können sogar Vorgänge, die in einer einzigen Anweisung abgeschlossen werden können, gestört werden.
Auf der X86 -Plattform bietet die CPU die Möglichkeit, den Bus während der Anweisungsausführung zu sperren. Auf dem CPU -Chip gibt es ein Lead #Hlockpin. Wenn das Präfix "Sperre" zu einer Anweisung im Assembler -Sprachprogramm hinzugefügt wird, wird der CPU des Montagemaschinens bei der Ausführung dieser Anweisung das Potenzial von #Hlockpin verringern und diese bis zum Ende dieser Anweisung freigeben, wodurch der Bus sperrt. Auf diese Weise kann ein anderer CPUs im selben Bus vorerst über den Bus nicht zugreifen, um die Atomizität dieser Anweisung in einer Multiprozessorumgebung zu gewährleisten. Natürlich können nicht alle Anweisungen mit Schloss vorangestellt werden. Nur hinzufügen, ADC und BTC, BTR, BTS, CMPXCHG, DEC, INC, NEG, NICHT oder, SBB, SUB, XOR, XADD und XCHG, können mit "Sperren" -Beweisungen vorangestellt werden, um atomische Operationen zu realisieren.
Der Kernbetrieb von Atomic ist CAS (VergleicheSet, implementiert mit dem CMPXCHG -Befehl, der eine Atomanweisung ist). Diese Anweisung hat drei Operanden, den Speicherwert V der Variablen (die Abkürzung des Wertes), den aktuellen erwarteten Wert E der Variablen (die Abkürzung der Ausnahme), der Wert U der Variablen (die Abkürzung der Aktualisierung). Wenn der Speicherwert dem aktuellen erwarteten Wert übereinstimmt, wird der aktualisierte Wert der Variablen durch die Variable überschrieben und der Pseudo-Code wie folgt ausgeführt.
if (v == e) {v = u return true} else {return false}Jetzt werden wir CAS -Operationen verwenden, um die oben genannten Probleme zu lösen. Thread B liest die Variable I im Speicher in eine temporäre Variable (unter der Annahme, dass der zu diesem Zeitpunkt gelesene Wert 0 ist) und dann den Wert von I in die arithmetische Einheit von Core1 in die arithmetische Betriebseinheit 1 liest. Als nächstes fügt 1 hinzu, ob der Wert in der temporären Variablen dem aktuellen Wert von i übereinstimmt. Wenn der Wert von I im Speicher mit dem Wert des Ergebniss in der Betriebseinheit (d. H. I+1) der gleiche ist (beachten Sie, dass es sich bei diesem Teil um eine CAS -Operation handelt, handelt es sich um eine atomare Operation, die nicht unterbrochen werden kann und der CAS -Betrieb in anderen Threads nicht gleichzeitig ausgeführt werden kann), andernfalls fällt die Anweisungsausführung fehl. Wenn die Anweisung fehlschlägt, bedeutet dies, dass Thread A den Wert von I durch 1. aus diesem Grund erhöht hat. Wir können sehen, dass, wenn der Wert von I durch beide Threads zu Beginn 0 ist, nur der CAS -Betrieb eines Threads erfolgreich sein kann, da die CAS -Operation nicht gleichzeitig ausgeführt werden kann. Bei Threads, die CAS -Operationen nicht ausführen, wird es definitiv erfolgreich sein, solange die CAS -Operationen laufend ausgeführt werden. Sie können sehen, dass es keinen Fadenblockieren gibt, was sich im Wesentlichen vom Synchronprinzip unterscheidet.
3. Einführung in das Atomic -Paket und die Quellcodeanalyse
Die Grundfunktion der Klasse im Atompaket ist, dass in einer Multi-Threaden-Umgebung, wenn mehrere Threads gleichzeitig mit einer einzigen (einschließlich Grundtypen und Referenztypen) arbeiten, exklusiv ist, wenn mehrere Threads den Wert der Variablen gleichzeitig aktualisieren.
Die Kernmethoden in der Atomic Series -Klasse werden in der unsicheren Klasse mehrere lokale Methoden bezeichnen. Wir müssen zuerst wissen, dass eine Sache die unsichere Klasse mit ihrem vollständigen Namen ist: sun.misc.unsafe. Diese Klasse enthält eine große Anzahl von Operationen im C -Code, einschließlich vieler direkter Speicherzuweisungen und Atomoperationen. Der Grund, warum es als Nichtsicherheit gekennzeichnet ist, besteht darin, Ihnen mitzuteilen, dass eine große Anzahl von Methodenaufrufen in diesem Bereich Sicherheitsrisiken besteht und sorgfältig verwendet werden muss, da dies sonst zu schwerwiegenden Folgen führt. Wenn Sie beispielsweise Speicher durch Unsicherheit zuweisen, kann dies dazu führen, dass einige Hinweise wie C ++ die Grenze zu anderen Prozessen überschreiten.
Klassen im Atompaket können gemäß dem operativen Datentyp in 4 Gruppen unterteilt werden.
AtomicBoolean,AtomicInteger,AtomicLong
Grundtypen von Atomoperationen für Thread-Safe
AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
Thread-safe Atombetrieb des Array-Typs, der nicht im gesamten Array, sondern auf einem einzelnen Element im Array arbeitet
AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
Thread-sichere Operationen basierend auf grundlegenden Typen (lange ganze Ganzzahl, Ganzzahl und Referenztyp) in Reflexionsprinzipelobjekten
AtomicReference,AtomicMarkableReference,AtomicStampedReference
Thread-sichere Referenztypen und Atomoperationen von Referenztypen, die ABA-Probleme verhindern
Wir verwenden im Allgemeinen Atomicinder, Atomicreference und AtomicStampedReference. Lassen Sie uns nun den Quellcode von Atomic Integer im Atomic -Paket analysieren. Die Quellcodes anderer Klassen sind grundsätzlich ähnlich.
1. Parameterkonstruktor
public atomicinteger (int initialValue) {value = initialValue;}Wie aus der Konstruktorfunktion ersichtlich ist, wird der Wert im Elementvariablenwert gespeichert
privater volatiler int -Wert;
Der Variablenwert der Elemente wird als flüchtiger Typ deklariert, der die Sichtbarkeit unter mehreren Threads zeigt, dh die Modifikation eines jeden Threads wird sofort in anderen Threads angezeigt.
2. CompareNDSet -Methode (Wert des Wertes wird durch interne und valueOffset übergeben)
public Final Boolean Vergleichst
Diese Methode ist der zentralste CAS -Betrieb
3.GetAndSet -Methode, bei der Vergleichs -Methode aufgerufen wird
public Final int getandset (int newValue) {für (;;) {int current = get (); if (vergleicheSet (aktuell, newValue)) Rückgabestrom; }}Wenn andere Threads den Wert des Wertes vor der Ausführung ändern, wenn (vergleicheSet (aktuell, NewValue), muss der Wert des Werts vom aktuellen Wert unterschiedlich sein. Wenn der VergleicheSet nicht ausführt, können Sie nur den Wert des Werts erneut beurteilen und dann weiter vergleichen, bis er erfolgreich ist.
4. Implementierung von i ++
public Final int getAndIncrement () {for (;;) {int current = get (); int next = current + 1; if (vergleicheSet (aktuell, nächst) zurücksend; }}5. Implementierung von ++ i
public Final int Incrementandget () {for (;;) {int current = get (); int next = current + 1; if (vergleicheSet (aktuell, nächst) zurückgeben; }}V.
Das folgende Programm verwendet AtomicInteger, um das Ticketverkaufsprogramm zu simulieren. Die beiden Programme verkaufen nicht das gleiche Ticket im laufenden Ergebnis und verkaufen die Tickets auch nicht als negativ.
Paket javaleanning; import Java.util.concurrent.atomic.atomicinteger; öffentliche Klasse SellTickets {Atomicinteger Tickets = new Atomicinteger (100); tickets.get (); if (tickets.comPareNDSet (TMP, TMP-1) {System.out.println (Thread.CurrentThread (). "Sellera"). Start (); neuer Thread (St.New Seller (), "SellerB"). Start ();}}5. ABA Problem
Das obige Beispiel führt das Ergebnis vollständig korrekt aus. Dies basiert auf der Tatsache, dass zwei (oder mehr) Threads in der gleichen Richtung auf Daten arbeiten. Im obigen Beispiel arbeiten beide Threads bei Dekremente mit Tickets. Wenn beispielsweise mehrere Threads Objektregistrierungsvorgänge in einer gemeinsamen Warteschlange ausführen, können die richtigen Ergebnisse über die Atomicreference -Klasse erzielt werden (dies ist tatsächlich der Fall für die in AQS gehaltene Warteschlange). Es können jedoch mehrere Threads eingeschrieben oder gelöscht werden, dh die Betriebsrichtung der Daten ist inkonsistent, sodass ABA auftreten kann.
Nehmen wir nun ein relativ leicht verständliches Beispiel, um das ABA-Problem zu erklären. Angenommen, es gibt zwei Threads T1 und T2, und diese beiden Threads führen Stapel- und Stapelvorgänge auf demselben Stapel durch.
Wir verwenden den durch Atomicreference definierten Schwanz, um die obere Position des Stapels zu speichern
Atomicreference <T> Schwanz;
Unter der Annahme, dass der T1 -Thread zum Stapeln von Operationen bereit ist, müssen wir nur die obere Position des Stacks von SP zu Newspen über den CAS -Betrieb aktualisieren, wie in Abbildung 1 gezeigt. Doch vor dem T1 -Thread wird Tail.comPareAnDSet (SP, Newsp) ausgeführt, und das System führt die Thread -Schedulierung aus und der T2 -Thread beginnt die Ausführung. T2 führt drei Operationen aus: A ist aus dem Stapel, B ist aus dem Stapel und dann ist A auf dem Stapel. Zu diesem Zeitpunkt beginnt das System erneut zu planen, und der T1 -Thread führt weiterhin die Stapeloperation durch, aber nach Ansicht des T1 -Threads ist das Element auf der Oberseite des Stapels immer noch ein (dh T1 glaubt immer noch, dass B immer noch das nächste Element auf der Spitze des Stapels ist. Der Zeiger des Stapels wird auf Knoten B. darauf hingewiesen. Tatsächlich existiert B im Stapel nicht mehr. Das Ergebnis nach T1 ist in Abbildung 3 dargestellt, was offensichtlich nicht das richtige Ergebnis ist.
6. Lösungen für ABA -Probleme
Verwenden Sie AtomicmarkableReference, AtomicStampedReference. Verwenden Sie die beiden oben genannten Atomklassen, um Operationen durchzuführen. Bei der Implementierung der Vergleichsset -Anweisung müssen sie nicht nur den vorherigen Wert und den erwarteten Wert des Objekts vergleichen, sondern auch den aktuellen (Betriebs-) Briefmarkenwert und den erwarteten (Betriebs-) Briefmarkenwert vergleichen. Nur wenn alles gleich ist, kann die Vergleiche -Methode erfolgreich sein. Jedes Mal, wenn das Update erfolgreich ist, ändert sich der Stempelwert und die Einstellung des Briefmarkenwerts wird vom Programmierer selbst gesteuert.
public boolean vergleiche (v erwartungsreferenz, v newReference, int erwartungsstempel, int newstamp) {pair <v> current = pair; return erwartungsreferenz == current.Reference && erwartestamp == current.stamp && ((newReference == current.Reference && Newstamp == aktuelle.Zu diesem Zeitpunkt erfordert die Vergleichs -Methode vier Parameter: Erwartung, NewReference, erwartete Stamp, Newstamp. Wenn wir diese Methode verwenden, müssen wir sicherstellen, dass der erwartete Stempelwert nicht mit dem Aktualisierungsstempelwert übereinstimmt. Normalerweise NewStamp = erwartetstamp+1
Nehmen Sie die obigen Beispiele
Angenommen, Thread T1 ist vor dem Stapel: SP zeigt auf A und der Stempelwert beträgt 100.
Thread T2 wird ausgeführt: Nach A nach A wird SP auf B und der Briefmarkenwert zu 101.
Nach der Freilassung von B zeigt SP auf C und der Briefmarkenwert wird zu 102.
Nachdem A in den Stapel gesteckt wurde, weist SP auf A und der Briefmarkenwert zu 103.
Thread T1 führt weiterhin die Vergleichserklärung aus und stellt fest, dass SP zwar immer noch auf A verweist, der erwartete Wert des Stempelwerts 100 jedoch von dem aktuellen Wert 103 unterscheidet. Daher schlägt der Vergleichssatz fehl. Sie müssen den Wert von Zeitungen erhalten (zu diesem Zeitpunkt verweist der Zeitsphalt auf C) und den erwarteten Wert des Briefmarkenwerts 103 und führt dann den Vergleichssatz erneut durch. Auf diese Weise wird ein erfolgreicher Stapel auf C. verweist auf C.
Beachten Sie, dass VergleicheStet, da der VergleicheSet nur einen Wert gleichzeitig ändern kann und NewReference und NewStamp gleichzeitig während der Implementierung nicht ändern kann, eine Paarklasse intern definiert ist, um NewReference und NewStamp in ein Objekt zu verwandeln. Bei der Durchführung von CAS -Operationen handelt es sich tatsächlich um eine Operation auf dem Paarobjekt.
private statische Klassenpaar <T> {endgültige t Referenz; endgültiger Int -Stempel; private Paar (t Referenz, int Stempel) {this.Reference = Reference; this.stamp = stempel; } static <T> Paar <T> von (t Referenz, int stempel) {neuer Paar <T> (Referenz, Stempel); }}Für AtomicmarkableReferenference ist der Stempelwert eine boolesche Variable, während der Stempelwert in AtomicStampedReference eine ganzzahlige Variable ist.
Zusammenfassen
In der oben genannten Art dreht sich alles um die kurze Diskussion dieses Artikels über die Implementierungsprinzipien und Anwendungen von Atompaketen in Java. Ich hoffe, es wird für alle hilfreich sein. Interessierte Freunde können weiterhin auf andere verwandte Themen auf dieser Website verweisen. Wenn es Mängel gibt, hinterlassen Sie bitte eine Nachricht, um darauf hinzuweisen.