In diesem Artikel wird hauptsächlich das Implementierungsbeispiel für die sperrenfreie Programmierung von cas-Anweisungen in der Java-Sprache vorgestellt. Es hat einen gewissen Referenzwert und Freunde in Not können mehr darüber erfahren.
Das erste Mal, dass Sie mit relevanten Inhalten in Kontakt kommen, ist das Schlüsselwort volatile. Sie wissen, dass es die Sichtbarkeit von Variablen gewährleisten und zur Implementierung atomarer Lese- und Schreiboperationen verwendet werden kann. . . Es gibt jedoch nichts, was Volatile tun kann, um einige zusammengesetzte Operationen zu implementieren. . . Die typischsten Vertreter sind Inkrementierungs- und Dekrementierungsoperationen. . . .
Wir wissen, dass in einer gleichzeitigen Umgebung der einfachste Weg, Datenkonsistenz zu erreichen, darin besteht, zu sperren, um sicherzustellen, dass nur ein Thread gleichzeitig mit den Daten arbeiten kann. . . . Ein Zähler kann beispielsweise folgendermaßen implementiert werden:
public class Counter { private volatile int a = 0; public synchronized int incrAndGet(int number) { this.a += number; return a; } public synchronized int get() { return a; } }
Wir modifizieren alle Operationen mit dem synchronisierten Schlüsselwort, um einen synchronen Zugriff auf das Attribut a sicherzustellen. . . Dies kann zwar die Konsistenz von a in einer gleichzeitigen Umgebung gewährleisten, aber aufgrund der Verwendung von Sperren, Sperren-Overhead, Thread-Planung usw. ist die Skalierbarkeit des Programms begrenzt, sodass es viele sperrenfreie Implementierungen gibt. . . .
Tatsächlich verwenden diese sperrfreien Methoden alle einige CAS-Anweisungen (Vergleichen und Wechseln), die vom Prozessor bereitgestellt werden. Was genau macht dieses CAS? Sie können die folgende Methode verwenden, um zu erklären, was CAS tut . Die dargestellte Semantik:
public synchronized int compareAndSwap(int expect, int newValue) { int old = this.a; if (old == expect) { this.a = newValue; } return old; }
Nun, das Gebot für die CAS-Semantik sollte durch den Code sehr klar sein. Es scheint, dass die meisten Prozessoren jetzt atomare CAS-Befehle implementieren. .
Okay, dann werfen wir einen Blick darauf, wo CAS in Java verwendet wird. Schauen wir uns zunächst den Typ AtomicInteger an. Dies ist ein Typ, der von der Parallelitätsbibliothek bereitgestellt wird:
private volatile int value;
Dies ist ein intern definiertes Attribut, das zum Speichern von Werten verwendet wird. Da es vom flüchtigen Typ ist, kann es die Sichtbarkeit zwischen Threads und die Atomizität des Lesens und Schreibens sicherstellen. . .
Dann werfen wir einen Blick auf einige der am häufigsten verwendeten Methoden:
public final int addAndGet(int delta) { for (;;) { int current = get(); int next = current + delta; if (compareAndSet(current, next)) return next; } }
Die Funktion dieser Methode besteht hier darin, Delta zum aktuellen Wert hinzuzufügen Sie können sehen, dass es in der gesamten Methode keine Sperre gibt. Dieser Code ist tatsächlich eine Methode zum Implementieren eines sperrenfreien Zählers in Java. Die Methode „compareAndSet“ ist wie folgt definiert:
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
Da die unsichere Methode aufgerufen wird, können Sie nichts dagegen tun. Tatsächlich sollten Sie erraten können, dass die JVM die CAS-Anweisung des Prozessors selbst aufruft, um atomare Operationen zu implementieren. . .
Grundsätzlich werden die wichtigen Methoden des Typs AtomicInteger sperrenfrei implementiert. . Daher kann die Verwendung dieses Typs in einer gleichzeitigen Umgebung eine bessere Leistung erzielen. . .
Das Obige wurde abgeschlossen, um einen sperrenfreien Zähler in Java zu implementieren. Schauen wir uns als nächstes an, wie man einen sperrenfreien Stapel implementiert und den Code direkt einfügt. Der Code wird aus „JAVA Concurrent Programming in Action“ nachgeahmt ":
package concurrenttest; import java.util.concurrent.atomic.AtomicReference; public class ConcurrentStack<e> { AtomicReference<node<e>> top = new AtomicReference<node<e>>(); public void push(E item) { Node<e> newHead = new Node<e>(item); Node<e> oldHead; while (true) { oldHead = top.get(); newHead.next = oldHead; if (top.compareAndSet(oldHead, newHead)) { return; } } } public E pop() { while (true) { Node<e> oldHead = top.get(); if (oldHead == null) { return null; } Node<e> newHead = oldHead.next; if (top.compareAndSet(oldHead, newHead)) { return oldHead.item; } } } private static class Node<e> { public final E item; public Node<e> next; public Node(E item) { this.item = item; } } }
Okay, der obige Code implementiert ganz einfach einen sperrenfreien Stapel. . . In einer gleichzeitigen Umgebung können sperrenfreie Datenstrukturen viel besser skaliert werden als Sperren. . .
Wenn wir über sperrenfreie Programmierung sprechen, müssen wir sperrenfreie Warteschlangen erwähnen. Tatsächlich wurde die Implementierung sperrenfreier Warteschlangen in der gleichzeitigen Bibliothek bereitgestellt: ConcurrentLinkedQueue.
public boolean offer(E e) { checkNotNull(e); final Node<e> newNode = new Node<e>(e); for (Node<e> t = tail, p = t;;) { Node<e> q = p.next; if (q == null) { // p is last node if (p.casNext(null, newNode)) { // Successful CAS is the linearization point // for e to become an element of this queue, // and for newNode to become "live". if (p != t) // hop two nodes at a time casTail(t, newNode); // Failure is OK. return true; } // Lost CAS race to another thread; re-read next } else if (p == q) // We have fallen off list. If tail is unchanged, it // will also be off-list, in which case we need to // jump to head, from which all live nodes are always // reachable. Else the new tail is a better bet. p = (t != (t = tail)) ? t : head; else // Check for tail updates after two hops. p = (p != t && t != (t = tail)) ? t : q; } }
Diese Methode wird verwendet, um Elemente am Ende der Warteschlange hinzuzufügen. Hier können Sie sehen, dass es für den spezifischen sperrfreien Algorithmus keine Sperre gibt. Michael-Scott hat einen nicht blockierenden Linked-List-Algorithmus vorgeschlagen. . . Um zu sehen, wie es konkret funktioniert, können Sie eine ausführlichere Einführung unter „JAVA Concurrent Programming in Practice“ finden.
Darüber hinaus werden andere Methoden tatsächlich sperrenfrei implementiert.
Schließlich ist es in der tatsächlichen Programmierung besser, diese sperrenfreien Implementierungen in einer gleichzeitigen Umgebung zu verwenden, da sie schließlich eine bessere Skalierbarkeit aufweisen.
Zusammenfassung
Das obige ist der detaillierte Inhalt vonBeispiel für die Implementierung einer sperrenfreien Programmierung von CAS-Anweisungen durch Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!