Ausführliche Erklärung zu Java-CAS

Grundlegende Verwendung von CAS

https://blog.csdn.net/Hsuxu/article/details/9467651 Die
CAS-Operation enthält drei Operanden-Speicherorte (V), den erwarteten ursprünglichen Wert (A) und den neuen Wert (B)
genau dann, wenn der erwartete Wert Wenn A und Speicherwert V sind gleich, Speicherwert V in B ändern, sonst nichts tun

public static void main(String[] args) {
    
    
    //主内存中 atomicInteger 的初始值为 5
    AtomicInteger atomicInteger = new AtomicInteger(5);

    // 如果初始值是5,那么将初始值修改为1024,然后得修改后的值
    System.out.println(atomicInteger.compareAndSet(5, 1024) + "主内存中的最终值为: " + atomicInteger.get());

    System.out.println(atomicInteger.compareAndSet(5, 2048) + "主内存中的最终值为: " + atomicInteger.get());
}

/**
 * this:当前对象
 * valueOffSet:当前对象的内存地址偏移量,就是this的内存地址
*/
public final boolean compareAndSet(int expect, int update) {
    
       
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

Das CAS in der JVM wird durch Aufrufen des JNI-Codes implementiert und durch Verwenden von C zum Aufrufen der zugrunde liegenden Anweisungen der CPU implementiert.

CAS Nachteile

https://blog.csdn.net/hsuxu/article/details/9467651
Obwohl CAS bei der Lösung atomarer Operationen sehr effizient ist, hat CAS immer noch drei Hauptprobleme. ABA-Problem, die Zykluszeit ist lang und der Overhead groß und kann nur den atomaren Betrieb einer gemeinsam genutzten Variablen garantieren

1> ABA-Problem

Da CAS prüfen muss, ob sich der nächste Wert beim Betrieb des Werts geändert hat, aktualisieren Sie ihn, wenn keine Änderung vorliegt. Wenn ein Wert jedoch ursprünglich A war, zu B und wieder zu A wird, werden Sie feststellen, wann Sie CAS verwenden check Sein Wert hat sich nicht geändert, aber er hat sich tatsächlich geändert. Die Lösung für das ABA-Problem besteht darin, die Versionsnummer zu verwenden. Fügen Sie die Versionsnummer vor der Variablen hinzu und fügen Sie bei jeder Aktualisierung der Variablen eine zur Versionsnummer hinzu. Dann wird A-B-A zu 1A-2B-3A.

Ab Java 1.5 bietet das JDK-Atompaket eine Klasse AtomicStampedReference, um das ABA-Problem zu lösen. Die Funktion der compareAndSet-Methode dieser Klasse besteht darin, zunächst zu überprüfen, ob die aktuelle Referenz der erwarteten Referenz entspricht und ob das aktuelle Flag dem erwarteten Flag entspricht. Wenn alle gleich sind, sind die Referenz und der Wert des Flags atomar auf den angegebenen Aktualisierungswert setzen.

Referenzdokumente für ABA-Probleme: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html

2> Lange Zykluszeit und hoher Overhead

Wenn Spin CAS für längere Zeit nicht erfolgreich ist, führt dies zu einem sehr hohen Ausführungsaufwand für die CPU

// CountDownLatch 核心方法
protected boolean tryReleaseShared(int releases) {
    
    
    // Decrement count; signal when transition to zero
    for (;;) {
    
    
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

Wenn die JVM den vom Prozessor bereitgestellten Pausenbefehl unterstützen kann, wird die Effizienz verbessert. Der
Pausenbefehl hat zwei Funktionen: Erstens kann er die De-Pipeline-Ausführung von Befehlen verzögern, so dass die CPU nicht zu viele Ausführungsressourcen verbraucht und Verzögerung. Die Zeit hängt von der spezifischen Implementierungsversion ab, und die Verzögerungszeit ist bei einigen Prozessoren Null. Zweitens kann das Leeren der CPU-Pipeline durch Verletzung der Speicherreihenfolge beim Verlassen der Schleife vermieden werden, wodurch die Effizienz der CPU-Ausführung verbessert wird.

3> Kann nur den atomaren Betrieb einer gemeinsam genutzten Variablen garantieren

Wenn Sie Operationen an einer gemeinsam genutzten Variablen ausführen, können Sie zyklisches CAS verwenden, um atomare Operationen sicherzustellen. Wenn Sie jedoch an mehreren gemeinsam genutzten Variablen arbeiten, kann zyklisches CAS die Atomizität der Operation nicht garantieren. Zu diesem Zeitpunkt können Sperren verwendet werden, oder es gibt ein The Eine schwierige Möglichkeit besteht darin, mehrere gemeinsam genutzte Variablen zu einer gemeinsam genutzten Variablen zu kombinieren. Zum Beispiel gibt es zwei gemeinsam genutzte Variablen i = 2, j = a, füge ij = 2a zusammen und verwende dann CAS, um ij zu betreiben. Ab Java 1.5 stellt JDK die AtomicReference-Klasse bereit, um die Atomizität zwischen referenzierten Objekten sicherzustellen. Sie können mehrere Variablen in ein Objekt einfügen, um CAS-Operationen auszuführen

AtomicReference

https://blog.csdn.net/qq_37385585/article/details/112778251
Paketwert und Stamp in Pair-Objekte zum Gesamtvergleich

private static void testStamped() {
    
    
    AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(5, 1);
    boolean result = true;
    
    result = stampedReference.compareAndSet(5,10,1,2);
    System.out.println(MessageFormat.format("Reference result:{0};value:{1};version:{2}",result, stampedReference.getReference(),stampedReference.getStamp()));
    
    result = stampedReference.compareAndSet(10,5,2,3);
    System.out.println(MessageFormat.format("Reference result:{0};value:{1};version:{2}",result,  stampedReference.getReference(),stampedReference.getStamp()));
    
    result = stampedReference.compareAndSet(5, 100, 1, 2);
    System.out.println(MessageFormat.format("Reference result:{0};value:{1};version:{2}",result,  stampedReference.getReference(),stampedReference.getStamp()));
}


/**
 * expectedReference the expected value of the reference
 * newReference the new value for the reference
 * expectedStamp the expected value of the stamp
 * newStamp the new value for the stamp
 */
public boolean compareAndSet(V   expectedReference,V   newReference,int expectedStamp,int newStamp) {
    
    
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
            newStamp == current.stamp) ||
            casPair(current, Pair.of(newReference, newStamp)));
}

private boolean casPair(Pair<V> cmp, Pair<V> val) {
    
    
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

Ich denke du magst

Origin blog.csdn.net/lewee0215/article/details/113100915
Empfohlen
Rangfolge