Die volatile Variable bietet Sichtweite und garantiert keine Gewindesicherheit und Atomizität.
Was ist Thread -Sichtbarkeit:
Sperren bieten zwei Hauptmerkmale: gegenseitige Ausschluss und Sichtbarkeit. Ein gegenseitiger Ausschluss bedeutet, dass nur ein Thread jeweils eine bestimmte Sperre enthalten kann, sodass diese Funktion zum Implementieren eines koordinierten Zugriffsprotokolls für gemeinsam genutzte Daten verwendet werden kann, sodass nur ein Thread die gemeinsam genutzten Daten gleichzeitig verwenden kann. Die Sichtbarkeit ist etwas komplexer und muss sicherstellen, dass Änderungen der gemeinsam genutzten Daten vor der Freisetzung der Sperre für einen anderen Thread sichtbar sind, der anschließend die Sperre erfasst - ohne diese Sichtbarkeitsgarantie, die durch den Synchronisationsmechanismus bereitgestellt wird, können die gemeinsamen Variablen, die vom Thread beobachtet werden, oder inkonsistente Werte, die viele schwerwiegende Probleme verursachen.
Siehe die Semantik von flüchtigem :
Volatil entspricht einer schwachen Implementierung von synchronisiertem Umsetzung, was bedeutet, dass volatile synchronisierte Semantik implementiert, aber keinen Sperrmechanismus aufweist. Es stellt sicher, dass Aktualisierungen des flüchtigen Feldes andere Threads auf vorhersehbare Weise informieren.
Volatile enthält die folgende Semantik:
(1) Das Java -Speichermodell ordnet die Operationen von valatilen Anweisungen nicht weiter: Dies stellt sicher, dass die Operationen mit volatilen Variablen in der Reihenfolge ausgeführt werden, in der die Anweisungen angezeigt werden.
(2) Die volatile Variable wird nicht in das Register (nur die Threads sind sichtbar) oder an anderen Orten zwischengespeichert, die für die CPU nicht sichtbar sind. Das Ergebnis der volatilen Variablen wird jedes Mal aus dem Hauptspeicher gelesen. Mit anderen Worten, für die Modifikation der flüchtigen Variablen sind andere Fäden immer sichtbar und verwenden keine Variablen im Fadenstapel. Das heißt, nach dem Schreiben einer valatilen Variablen kann jeder nachfolgende Lesevorgang verstanden werden, um das Ergebnis dieser Schreiboperation anzuzeigen.
Obwohl die Eigenschaften der flüchtigen Variablen gut sind, kann flüchtig keine Gewindesicherheit garantieren. Das heißt, der Betrieb des flüchtigen Feldes ist nicht atomar. Die volatile Variable kann nur die Sichtbarkeit garantieren (andere Threads können die Ergebnisse verstehen, nachdem diese Änderung nach einem Thread geändert wurde). Um die Atomizität zu gewährleisten, können Sie sie nur so weit einsperren!
Prinzipien zur Verwendung von volatilen:
Drei Prinzipien für die Anwendung volatiler Variablen:
(1) Schreibvariablen hängen nicht vom Wert dieser Variablen ab, oder nur ein Thread modifiziert diese Variable
(2) Der Zustand der Variablen muss nicht an den invarianten Einschränkungen mit anderen Variablen teilnehmen
(3) Zugriffsvariablen müssen nicht gesperrt werden
Tatsächlich zeigen diese Bedingungen, dass diese gültigen Werte, die in die variable Variable geschrieben werden können, unabhängig vom Status eines beliebigen Programms sind, einschließlich des aktuellen Zustands der Variablen.
Die ersten Bedingungsgrenzen verhindern, dass die flüchtige Variable als Gewindefach verwendet wird. Obwohl der inkrementelle Vorgang (X ++) wie ein separater Vorgang aussieht, ist es tatsächlich eine kombinierte Operation, die aus einer Abfolge von read-modifizierenden Schreibemaßnahmen besteht, die atomisch durchgeführt werden müssen, und flüchtig kann nicht die erforderlichen Atomeigenschaften bereitgestellt werden. Um den korrekten Betrieb zu implementieren, muss der Wert von X während des Betriebs konstant bleiben, was mit der volatilen Variablen nicht möglich ist. (Wenn der Wert jedoch nur aus einem einzigen Thread geschrieben wird, kann die erste Bedingung ignoriert werden.)
Die meisten Programmiersituationen widersprechen einer dieser drei Bedingungen, sodass die flüchtige Variable nicht so universell für die Synchronisierung der Gewinde anwendbar anwendbar ist. Listing 1 zeigt eine nicht-thread-safe numerische Reichweite. Es enthält eine Invariante - die untere Grenze ist immer geringer als oder gleich der Obergrenze.
Verwenden Sie volatile richtig:
Modus Nr. 1: Statusflags
Möglicherweise besteht die Spezifikation zur Implementierung der volatilen Variablen darin, einfach ein boolesches Status-Flag zu verwenden, um anzuzeigen, dass ein wichtiges einmaliges Ereignis aufgetreten ist, z. B. die Abschluss der Initialisierung oder die Anforderung von Ausfallzeiten.
Viele Anwendungen enthalten eine Kontrollstruktur in Form von "Führen Sie einige Arbeiten aus, wenn das Programm nicht anhalten ist", wie in Listing 2 gezeigt:
Listing 2. Verwenden Sie die flüchtige Variable als Statusflag
volatile boolesche Abschaltung; … Public void stilldown () {stilldownRequested = true; } public void dowork () {while (! stilldownRequested) {// do Sachen}}Es ist sehr wahrscheinlich, dass die Shutdown () -Methode von außerhalb der Schleife aufgerufen wird - d. H. In einem anderen Thread -, sodass eine Art von Synchronisation durchgeführt werden muss, um sicherzustellen, dass die Sichtbarkeit der Variablen für Shutdown -Rechen korrekt implementiert wird. (Kann von JMX Listener, Operation Listener im GUI -Event -Thread über RMI, über einen Webdienst usw. aufgerufen werden. Das Schreiben einer Schleife mit einem synchronisierten Block ist jedoch viel mehr störender als das Schreiben mit dem in Listing 2 angezeigten volatilen Statusflag. Da die volatile Codierung vereinfacht und die Statusflags nicht von anderen Zuständen innerhalb des Programms abhängen, ist es hier sehr geeignet für volatile.
Ein gemeinsames Merkmal dieser Art von Zustands -Tag ist, dass es normalerweise nur einen Zustandsübergang gibt. Die Flagge zum Herunterfahren wird von Falsch zu True konvertiert und das Programm stoppt. Dieses Muster kann auf die Statusflagge des Hin- und Herübergangs erweitert werden, kann jedoch nur dann erweitert werden, wenn die Übergangszeit nicht bemerkt wird (von falsch zu wahr, dann zu falsch). Darüber hinaus sind einige Atomzustandsumwandlungsmechanismen erforderlich, wie z. B. Atomvariablen.
Modus Nr. 2: einmalige sichere Veröffentlichung
Die mangelnde Synchronisation kann zu unerreichbarer Sichtbarkeit führen, was es schwieriger macht, zu bestimmen, wann eine Objektreferenz anstelle eines primitiven Wertes geschrieben werden soll. In Abwesenheit einer Synchronisation kann ein aktualisierter Wert, auf den ein Objekt (geschrieben von einem anderen Thread) verwiesen wird, auftreten, und der alte Wert des Zustands dieses Objekts existiert gleichzeitig. (Dies ist die Grundursache für das berühmte Problem mit doppelter Überprüfung, bei dem Objektreferenzen ohne Synchronisation gelesen werden, was zu dem Problem führt, dass Sie möglicherweise eine aktualisierte Referenz sehen, aber dennoch ein unvollständig konstruiertes Objekt durch diese Referenz sehen).
Eine Technik zur Implementierung einer sicheren Veröffentlichung von Objekten besteht darin, Objektreferenzen als flüchtiger Typ zu definieren. Listing 3 zeigt ein Beispiel, in dem der Hintergrund -Thread während des Starts einige Daten aus der Datenbank lädt. Andere Codes, wenn sie diese Daten verwenden können, prüfen Sie, ob sie vor der Verwendung veröffentlicht wurden.
Auflistung 3. Verwenden der volatilen Variablen für eine einmalige sichere Veröffentlichung
öffentliche Klasse Hintergrundfloobleloader {public volatile Flohloble theFlooble; public void initinbackground () {// viele Dinge machen theflooble = new floaneble (); // Dies ist das einzige Schreiben an die Flooble}} public Class Eine andere Klasse {public void dowork () {while (true) {// Mach ein paar Dinge… // benutze das Floavel, aber nur, wenn es bereit ist, wenn (Floobleleloader). }}}Wenn die Flooble -Referenz kein flüchtiger Typ ist, erhält der Code in Dowork () ein unvollständig konstruiertes Flohlier, wenn er die Flooble nicht regelt.
Eine notwendige Bedingung für dieses Muster ist, dass das veröffentlichte Objekt thread-sicher oder ein gültiges unveränderliches Objekt sein muss (effektive unveränderliche Mittelwerte, dass der Status des Objekts nach der Veröffentlichung niemals geändert wird). Referenzen des volatilen Typs gewährleisten die Sichtbarkeit des Veröffentlichungsformulars des Objekts. Eine zusätzliche Synchronisation ist jedoch erforderlich, wenn sich der Zustand des Objekts nach der Veröffentlichung ändert.
Muster Nr. 3: Unabhängige Beobachtung
Eine weitere einfache Art, volatile Verwendung sicher zu verwenden, besteht darin, die Beobachtungen regelmäßig für die interne Verwendung des Programms zu "freigeben". Angenommen, es gibt einen Umgebungssensor, der die Umgebungstemperatur erkennen kann. Ein Hintergrund -Thread kann den Sensor alle paar Sekunden lesen und die volatile Variable mit dem aktuellen Dokument aktualisieren. Dann können andere Threads diese Variable lesen, damit sie jederzeit den neuesten Temperaturwert sehen können.
Eine andere Anwendung, die diesen Modus verwendet, besteht darin, die Statistiken des Programms zu sammeln. Listing 4 zeigt, wie sich der Authentifizierungsmechanismus an den Namen des Benutzers erinnert, der zum letzten Mal angemeldet war. Wiederholen Sie den letzten Verweis, um Werte für andere Teile des Programms zu veröffentlichen.
Listing 4. Verwenden der volatilen Variablen für die Veröffentlichung mehrerer unabhängiger Beobachtungen
public class userManager {public volatile String lastUser; public boolean authenticate (String -Benutzer, String -Passwort) {boolean valid = passwordisvalid (Benutzer, Passwort); if (gültig) {user u = new user (); ActiveUsers.Add (u); lastUser = user; } gültig zurück; }}Dieser Modus ist eine Erweiterung des vorherigen Modus. Veröffentlichung eines bestimmten Wertes für die Verwendung an anderer Stelle im Programm, aber im Gegensatz zu einer einmaligen Veranstaltung handelt es sich um eine Reihe unabhängiger Ereignisse. Dieses Muster erfordert, dass der veröffentlichte Wert gültig und unveränderlich ist - dh der Status des Wertes ändert sich nach der Veröffentlichung nicht. Der Code, der diesen Wert verwendet, muss klar sein, dass sich der Wert jederzeit ändern kann.
Modus Nr. 4: "Volatile Bean" -Modus
Das flüchtige Bohnenmuster eignet sich für Frameworks, die JavaBeans als "Ehrenstruktur" verwenden. Im flüchtigen Bohnenmuster werden JavaBeans als Set von Behältern mit unabhängigen Eigenschaften von Getter- und/oder Setter -Methoden verwendet. Das Grundprinzip des flüchtigen Bohnenmusters besteht darin, dass viele Frameworks Container für Inhaber von volatilen Daten (z. B. httpSession) bereitstellen, die in diesen Containern platzierten Objekte jedoch mit Gewinde-Sicherheit sein müssen.
Im volatilen Bean -Modus sind alle Datenelemente eines Javabäer von flüchtigem Typ, und die Getter- und Setter -Methoden müssen sehr gewöhnlich sein - sie können keine Logik enthalten, außer um entsprechende Eigenschaften zu erhalten oder festzulegen. Darüber hinaus muss das referenzierte Objekt für die vom Objekt verwiesenen Datenelemente gültig und unveränderlich sein. (Dies verbietet die Eigenschaften mit Array -Werten, denn wenn eine Array -Referenz als volatil deklariert wird, hat nur die Referenz und nicht das Array selbst volatile Semantik). Für jede volatile Variable können Invarianten oder Einschränkungen keine javabäischen Eigenschaften enthalten. Die Beispiele in Listing 5 zeigen JavaBeans, die sich an das flüchtige Bohnenmuster halten:
Modus Nr. 4: "Volatile Bean" -Modus
@Threadsafe öffentliche Klasse Person {private volatile String FirstName; private volatile Zeichenfolge Lastname; privates volatiles Int -Alter; public String getFirstName () {return firstName; } public String getLastName () {return LastName; } public int getage () {return ay; } public void setFirstName (String FirstName) {this.firstname = firstName; } public void setLastName (String LastName) {this.lastName = lastName; } public void setage (int age) {this.age = älter; }}Fortgeschrittener Modus der volatilen
Die in den vorherigen Abschnitten beschriebenen Muster decken die meisten grundlegenden Anwendungsfälle ab und die Verwendung von volatilen in diesen Mustern ist sehr nützlich und einfach. In diesem Abschnitt wird ein fortgeschrittenerer Modus eingeführt, in dem volatile Leistung oder Skalierbarkeitsvorteile bietet.
Die fortschrittlichen Modi der flüchtigen Anwendungen sind sehr zerbrechlich. Daher müssen die Annahmen sorgfältig nachgewiesen werden, und diese Muster sind streng eingedämmt, da selbst sehr kleine Änderungen Ihren Code beschädigen können! In ähnlicher Weise ist der Grund für die Verwendung eines fortschrittlicheren volatilen Anwendungsfalls, dass es die Leistung verbessern kann, um sicherzustellen, dass Sie wirklich feststellen, dass Sie diesen Leistungsvorteil erzielen müssen, bevor Sie die erweiterten Muster anwenden. Es gibt Kompromisse zu diesen Mustern, die Lesbarkeit oder Wartbarkeit im Austausch für mögliche Leistungsgewinne aufgeben-wenn Sie keine Leistungsverbesserungen benötigen (oder nicht beweisen können, dass Sie es über ein strenges Testprogramm benötigen), ist es wahrscheinlich ein schlechtes Angebot, da Sie wahrscheinlich weniger Geld verlieren und etwas weniger wert sind als das, was Sie aufgeben.
Modus Nr. 5: Les-Schreiben-Schlossstrategie mit niedrigem Overhead
Bisher sollten Sie verstehen, dass volatil nicht in der Lage ist, Zähler zu implementieren. Da ++ X tatsächlich eine einfache Kombination aus drei Vorgängen ist (lesen, hinzufügen, speichern), wenn mehrere Threads gleichzeitig inkrementelle Vorgänge im flüchtigen Zähler ausführen, kann der aktualisierte Wert verloren gehen.
Wenn der Lesevorgang jedoch viel mehr als der Schreibvorgang ist, können Sie eine interne Sperre und flüchtige Variablen verwenden, um den Overhead des öffentlichen Codepfads zu verringern. Die in Listing 6 angezeigten Thread-Safe-Zähler verwenden synchronisiert, um sicherzustellen, dass die inkrementellen Vorgänge atomar und flüchtig sind, um die Sichtbarkeit des aktuellen Ergebnisses sicherzustellen. Diese Methode kann eine bessere Leistung erzielen, wenn Aktualisierungen nicht häufig sind, da der Overhead von Lesepfaden nur den volatilen Lesevorgang beinhaltet, der normalerweise besser ist als der Overhead einer wettbewerbsfreien Schlosserakquisition.
Auflistung 6. Verwenden Sie volatil und synchronisiert, um "Unterkopf-Leseschreiber" zu erreichen
@Threadsafe Public Class CheeseyCounter {// verwendet den billigen Les-Write-Lock-Trick // Alle mutativen Operationen müssen mit dem "this" -Schress @guardedby ("this") Private volatile int-Wert durchgeführt werden; public int getValue () {Rückgabewert; } public synchronisierte int Increment () {Rückgabewert ++; }}Der Grund, warum diese Technik als "niedrigeres Overhead-Lese-Schreibschloss" bezeichnet wird, liegt darin, dass Sie einen anderen Synchronisationsmechanismus für Leseschreiben verwenden. Da der Schreibvorgang in diesem Beispiel gegen die erste Bedingung für die Verwendung von Flüchteln verstößt, kann der Zähler nicht sicher mit volatilen implementiert werden - Sie müssen ein Schloss verwenden. Sie können jedoch volatile in Lesevorgängen verwenden, um die Sichtbarkeit des aktuellen Werts zu gewährleisten, damit Sie Sperren verwenden können, um alle Änderungen und schreibgeschützt mit volatilen Durchführungen durchzuführen. Unter ihnen ermöglicht das Schloss nur einen Thread jeweils auf den Wert, und volatil können mehrere Threads Lesevorgänge ausführen. Wenn Sie volatile verwenden, um sicherzustellen, dass der Codepfad gelesen wird, wird dies mehr Freigabe als die Verwendung der Sperre zum Ausführen aller Codepfade - genau wie eine Leseschreiberoperation. Denken Sie jedoch an die Schwächen dieses Modells: Wenn die grundlegendste Anwendung dieses Modells überlegt ist, wird es sehr schwierig, diese beiden konkurrierenden Synchronisationsmechanismen zu kombinieren.
Über die Neuordnung der Anweisungen und die vorne vorangegangene Regel
1. Nachdenken
Die Java -Sprachspezifikation sieht vor, dass der JVM -Thread intern die sequentielle Semantik beibehält, dh, sofern das Endergebnis des Programms seinen Ergebnissen in einer strikten aufeinanderfolgenden Umgebung entspricht, kann die Ausführungsreihenfolge der Anweisungen mit der Reihenfolge des Code nicht übereinstimmen. Dieser Vorgang wird durch einen Befehl neu angeordnet. Die Bedeutung der Anweisung der Anleitung besteht darin, dass die JVM die Merkmale des Prozessors (CPU-Multi-Level-Cache-System, Multi-Core-Prozessor usw.) angemessen neu ordnen kann, so dass die Maschinenanweisungen eher den Ausführungseigenschaften der CPU übereinstimmen und die Leistung der Maschine maximieren.
Das einfachste Modell für die Programmausführung ist die Ausführung in der Reihenfolge, in der die Anweisungen angezeigt werden, was von der CPU unabhängig ist, die die Anweisungen ausführt und die Portabilität der Anweisungen im größten Teil sicherstellt. Der professionelle Begriff für dieses Modell wird als sequentielles Konsistenzmodell bezeichnet. Moderne Computersysteme und Prozessorarchitekturen garantieren dies jedoch nicht (da die künstliche Bezeichnung nicht immer die Einhaltung der CPU -Verarbeitungsmerkmale garantieren kann).
2. Appen- und Vorre-Regel
Das Java-Speichermodell verfügt über ein Prinzip, das heißt, wenn Aktion B das Ausführungsergebnis der Aktion A sehen möchte (unabhängig davon, ob A/B im selben Thread ausgeführt wird), muss A/B die gescheiterte Beziehung erfüllen.
Vor der Einführung der Vorderregel ein Konzept: JMM-Aktion (Java Memeory Model Action) einführen, speichert Java Modellaktionen. Eine Aktion enthält: Lesen und Schreiben von Variablen, Überwachung von Sperren und Freigabe von Sperren, Thread start () und join (). Das Schloss wird später erwähnt.
passiert vor vollständige Regeln:
(1) Jede Aktion im selben Thread erfolgt vor einer Aktion, die danach erscheint.
(2) Entsperren eines Monitors findet vor Ort auf jeder nachfolgenden Sperre auf demselben Monitor statt.
(3) Schreiben Sie den Vorgang in das flüchtige Feld vor, vor dem jeder nachfolgende Lesevorgang desselben Feldes vorhanden ist.
(4) Der Anruf zum Thread.Start () wird vor den Aktionen im Start-Thread geschieht.
(5) Alle Aktionen im Thread werden vor Ort überprüft, um diesen Thread zu beenden oder in Thread zu beenden.
(6) Ein Thread A nennt den Interrupt () eines anderen Threads B, bevor Faden A stellt, dass b durch a unterbrochen wird (b wirft eine Ausnahme aus oder A erkennt, B ist interruptiert () oder unterbrochen ()).
(7) Das Ende eines Objektkonstruktors tritt vor und der Beginn des Finalizers des Objekts
(8) Wenn eine Aktion stattfindet, ist es in Aktion B und B-Aktion vor und C-Aktion, dann ist eine Aktion vorhanden.
Das obige dreht sich alles um diesen Artikel. Ich werde es Ihnen hier vorstellen. Ich hoffe, es wird für Sie hilfreich sein, um volatile Variablen in Java zu lernen und zu verstehen.