Heim  >  Artikel  >  Java  >  Analyse von Cas-Operationen in Java

Analyse von Cas-Operationen in Java

黄舟
黄舟Original
2017-09-15 10:52:391529Durchsuche

Dieser Artikel analysiert die Konzepte, Prinzipien und Verwendung von Cas-Operationen in der Java-Programmierung anhand von Beispielen. Er hat einen gewissen Referenzwert und Freunde in Not können mehr darüber erfahren.

CAS bezieht sich auf eine spezielle Anweisung, die von modernen CPUs weithin unterstützt wird und mit gemeinsam genutzten Daten im Speicher arbeitet. Diese Anweisung führt atomare Lese- und Schreibvorgänge für gemeinsam genutzte Daten im Speicher aus.

Eine kurze Einführung in den Ablauf dieser Anweisung: Zunächst vergleicht die CPU die zu ändernden Daten im Speicher mit dem erwarteten Wert. Wenn dann die beiden Werte gleich sind, ersetzt die CPU den Wert im Speicher durch den neuen Wert. Andernfalls wird kein Vorgang ausgeführt. Schließlich gibt die CPU den alten Wert zurück. Diese Reihe von Operationen ist atomar. Obwohl sie kompliziert erscheinen, sind sie der Hauptgrund dafür, dass der Parallelitätsmechanismus von Java 5 besser ist als der ursprüngliche Sperrmechanismus. Einfach ausgedrückt lautet die Bedeutung von CAS: „Was sollte meiner Meinung nach der ursprüngliche Wert sein? Wenn ja, aktualisieren Sie den ursprünglichen Wert auf den neuen Wert, andernfalls ändern Sie ihn nicht und sagen Sie mir, wie hoch der ursprüngliche Wert ist.“ (Diese Beschreibung stammt aus „Java Concurrent Programming Practice“)

Einfach ausgedrückt verfügt CAS über drei Operanden: den Speicherwert V, den alten erwarteten Wert A und den neuen zu ändernden Wert B . Nur wenn der erwartete Wert A und der Speicherwert V gleich sind, ändern Sie den Speicherwert V in B, andernfalls geben Sie V zurück. Dies ist eine optimistische Sperridee. Es wird angenommen, dass kein anderer Thread sie ändern wird, bevor sie geändert wird. Es wird davon ausgegangen, dass andere Threads sie ändern werden, bevor sie geändert wird.

Sehen wir uns ein einfaches Beispiel an:


if(a==b) { 
  a++; 
}

Stellen Sie sich vor, was wäre, wenn der Wert von a geändert würde, bevor a++ ausgeführt würde? Wird a++ noch ausgeführt? Der Grund für dieses Problem liegt darin, dass sich der Wert von a in einer Multithread-Umgebung in einem unsicheren Zustand befindet. Sperren können solche Probleme lösen, CAS kann sie aber auch ohne Sperren lösen.


int expect = a; 
if(a.compareAndSet(expect,a+1)) { 
  doSomeThing1(); 
} else { 
  doSomeThing2(); 
}

Auf diese Weise wird a++ nicht ausgeführt, wenn der Wert von a geändert wird.

Gemäß der obigen Schreibmethode wird a++ nicht ausgeführt. Was ist, wenn wir immer noch eine ++-Operation ausführen möchten? 🎜>


while(true) { 
  int expect = a; 
  if (a.compareAndSet(expect, a + 1)) { 
    doSomeThing1(); 
    return; 
  } else { 
    doSomeThing2(); 
  } 
}
Mit der oben genannten Schreibmethode wird eine ++-Operation ohne Sperre implementiert. Dies ist tatsächlich ein nicht blockierender Algorithmus.

Anwendung

Fast die meisten Klassen im Paket java.util.concurrent.atomic verwenden CAS-Operationen, nehmen Sie AtomicInteger als Beispiel, siehe Look bei der Implementierung mehrerer seiner Hauptmethoden:



public final int getAndSet(int newValue) { 
  for (;;) { 
    int current = get(); 
    if (compareAndSet(current, newValue)) 
      return current; 
  } 
}
Die Erklärung in der JDK-Dokumentation zur getAndSet-Methode lautet: atomar auf einen bestimmten Wert setzen und alt zurückgeben Wert. Wo sich die atomare Methode widerspiegelt, spiegelt sie sich in CompareAndSet wider. Sehen wir uns an, wie CompareAndSet implementiert wird:


public final boolean compareAndSet(int expect, int update) { 
  return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 
}
Wie erwartet wird die Unsafe-Klasse verwendet. Die CAS-Operation ist vollendet.


Sehen wir uns an, wie die a++-Operation implementiert wird:


public final int getAndIncrement() { 
  for (;;) { 
    int current = get(); 
    int next = current + 1; 
    if (compareAndSet(current, next)) 
      return current; 
  } 
}
Es ist fast genau dasselbe wie das ursprüngliche Beispiel. und verwendet auch die CAS-Operation, um die automatische Inkrementierung zu implementieren.


++Eine Operation ähnelt einer++-Operation, außer dass die Rückgabeergebnisse unterschiedlich sind


public final int incrementAndGet() { 
  for (;;) { 
    int current = get(); 
    int next = current + 1; 
    if (compareAndSet(current, next)) 
      return next; 
  } 
}
Außerdem java.util .concurrent. Die ConcurrentLinkedQueue-Klasse verwendet einen nicht blockierenden Algorithmus, der keine Sperren verwendet und vollständig auf CAS-Operationen basiert. Man kann sagen, dass der CAS-Betrieb die Grundlage des JAVA-Parallelitäts-Frameworks ist, und das Design des gesamten Frameworks basiert auf dem CAS-Betrieb.

Nachteile:

1. ABA-Problem

Wikipedia gibt ein lebendiges Beispiel -


Das sind Sie Am Flughafen kommt eine heiße und sexy Schönheit vorbei, und wenn du nicht aufpasst, benutzt sie den gleichen, den ich mit deinem Koffer getauscht habe voller Geld und bist dann gegangen. Du hast gesehen, dass dein Koffer noch da war, also hast du den Koffer genommen und bist zum Flugzeug gefahren.


Das ist das Problem mit ABA.

CAS-Operationen können leicht zu ABA-Problemen führen, das heißt, zwischen der Ausführung von a++ wurde a möglicherweise von mehreren Threads geändert, aber es ist gerade auf den ursprünglichen Wert zurückgekehrt. Zu diesem Zeitpunkt wird CAS denken, dass die Der Wert von a hat sich nicht geändert. a Nachdem Sie eine Weile draußen herumgelaufen sind, können Sie sicher sein, dass es nichts Schlimmes angerichtet hat, nein! ! Vielleicht zum Spaß verringert es den Wert von b, erhöht den Wert von c usw. Wenn a ein Objekt ist, kann dieses Objekt neu erstellt werden, und a ist eine Referenz. Wie ist die Situation? Es gibt immer noch viele Möglichkeiten, das ABA-Problem zu lösen. Sie können auch die Einführung einer Versionsnummer in Betracht ziehen, wenn die Änderungsanzahl unverändert bleibt. , a++-Operationen werden nur ausgeführt, wenn die Versionsnummern gleich sind. Dies ähnelt in gewisser Weise der Transaktionsatomaritätsverarbeitung!


2. Es verbraucht mehr CPU-Ressourcen und erledigt einige nutzlose Arbeit, auch wenn es keinen Konflikt gibt.


3. Es erhöht die Komplexität des Programmtests und es können Probleme auftreten, wenn Sie nicht vorsichtig sind.

Zusammenfassung

CAS kann verwendet werden, um atomare Operationen ohne Sperren zu implementieren, aber die Anwendungsszenarien müssen klar definiert sein. Für sehr einfache Operationen ohne Einführung von Sperren können Sie die Verwendung von CAS-Operationen in Betracht ziehen, wenn Sie eine durchführen möchten Betrieb nicht blockierend. Es wird nicht empfohlen, CAS bei komplexen Vorgängen einzuführen, da das Programm dadurch weniger lesbar und schwieriger zu testen ist und ABA-Probleme auftreten.

Das obige ist der detaillierte Inhalt vonAnalyse von Cas-Operationen in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn