Das Buch "Java Concurrency in Practice"

BildHallo habrozhiteli! Streams sind ein wesentlicher Bestandteil der Java-Plattform. Multi-Core-Prozessoren sind an der Tagesordnung, und die effektive Nutzung der ParallelitĂ€t ist erforderlich geworden, um eine Hochleistungsanwendung zu erstellen. Eine verbesserte virtuelle Java-Maschine, die UnterstĂŒtzung von Hochleistungsklassen und eine Vielzahl von Bausteinen fĂŒr Parallelisierungsaufgaben waren einst ein Durchbruch bei der Entwicklung paralleler Anwendungen. In Java Concurrency in Practice erklĂ€ren die Entwickler der bahnbrechenden Technologie selbst nicht nur, wie sie funktionieren, sondern sprechen auch ĂŒber Entwurfsmuster. Es ist einfach, ein wettbewerbsfĂ€higes Programm zu erstellen, das zu funktionieren scheint. Das Entwickeln, Testen und Debuggen von Multithread-Programmen wirft jedoch viele Probleme auf. Der Code funktioniert nicht mehr, wenn es am wichtigsten ist: unter starker Last.In „Java Concurrency in Practice“ finden Sie sowohl theoretische als auch spezifische Methoden zum Erstellen zuverlĂ€ssiger, skalierbarer und unterstĂŒtzter paralleler Anwendungen. Die Autoren bieten keine Liste von APIs und ParallelitĂ€tsmechanismen an, sondern fĂŒhren Entwurfsregeln, Muster und Modelle ein, die von der Java-Version unabhĂ€ngig sind und ĂŒber viele Jahre hinweg relevant und effektiv bleiben.

Auszug. Gewindesicherheit


Sie werden ĂŒberrascht sein, dass wettbewerbsfĂ€hige Programmierung mit Gewinden oder Schlössern (1) nicht mehr verbunden ist als Tiefbau mit Nieten und I-TrĂ€gern. NatĂŒrlich erfordert der Bau von BrĂŒcken die korrekte Verwendung einer großen Anzahl von Nieten und I-TrĂ€gern, und das Gleiche gilt fĂŒr den Bau von Wettbewerbsprogrammen, bei denen Gewinde und Schlösser korrekt verwendet werden mĂŒssen. Dies sind jedoch nur Mechanismen - Mittel zur Erreichung des Ziels. Das Schreiben von thread-sicherem Code steuert im Wesentlichen den Zugriff auf einen Status und insbesondere auf einen verĂ€nderlichen Status.

Im Allgemeinen sind der Status eines Objekts seine Daten, die in Statusvariablen wie Instanz- und statischen Feldern oder Feldern von anderen abhÀngigen Objekten gespeichert sind. Der Status des HashMap-Hashs wird teilweise in der HashMap selbst, aber auch in vielen Map.Entry-Objekten gespeichert. Der Status eines Objekts enthÀlt alle Daten, die sein Verhalten beeinflussen können.

(1) lock block, «», , . blocking. lock «», « ». lock , , , «». — . , , , . — . . .

Mehrere Threads können mutiert auf eine gemeinsam genutzte Variable zugreifen - Ă€ndert ihren Wert. TatsĂ€chlich versuchen wir, Daten und nicht Code vor unkontrolliertem Wettbewerbszugriff zu schĂŒtzen.

Das Erstellen eines thread-sicheren Objekts erfordert eine Synchronisierung, um den Zugriff auf einen mutierten Zustand zu koordinieren. Eine NichterfĂŒllung kann zu Datenkorruption und anderen unerwĂŒnschten Folgen fĂŒhren.

Immer wenn mehr als ein Thread auf eine Statusvariable zugreift und einer der Threads möglicherweise darauf schreibt, mĂŒssen alle Threads ihren Zugriff mithilfe der Synchronisation koordinieren. Die Synchronisation in Java wird durch das synchronisierte SchlĂŒsselwort bereitgestellt, das exklusive Sperren sowie flĂŒchtige und atomare Variablen und explizite Sperren bietet.

Widerstehen Sie der Versuchung zu glauben, dass es Situationen gibt, die keine Synchronisation erfordern. Das Programm kann arbeiten und seine Tests bestehen, bleibt jedoch fehlerhaft und stĂŒrzt jederzeit ab.

Wenn mehrere Threads ohne ordnungsgemĂ€ĂŸe Synchronisierung mit einem mutierten Status auf dieselbe Variable zugreifen, funktioniert Ihr Programm nicht richtig. Es gibt drei Möglichkeiten, dies zu beheben:

  • Teilen Sie die Statusvariable nicht in allen Threads
  • die Zustandsvariable nicht verĂ€nderbar machen;
  • Verwenden Sie die Statussynchronisation jedes Mal, wenn Sie auf die Statusvariable zugreifen.

Korrekturen erfordern möglicherweise erhebliche DesignÀnderungen. Daher ist es viel einfacher, eine Klasse sofort threadsicher zu entwerfen, als sie spÀter zu aktualisieren.

Ob mehrere Threads auf diese oder jene Variable zugreifen, ist schwer herauszufinden. GlĂŒcklicherweise helfen objektorientierte technische Lösungen, die beim Erstellen gut organisierter und einfach zu pflegender Klassen helfen, z. B. das Einkapseln und Ausblenden von Daten, auch beim Erstellen threadsicherer Klassen. Je weniger Threads Zugriff auf eine bestimmte Variable haben, desto einfacher ist es, die Synchronisierung sicherzustellen und die Bedingungen festzulegen, unter denen auf diese Variable zugegriffen werden kann. Die Java-Sprache zwingt Sie nicht dazu, den Status zu kapseln. Es ist durchaus akzeptabel, den Status in öffentlichen Feldern (auch in öffentlichen statischen Feldern) zu speichern oder einen Link zu einem Objekt zu veröffentlichen, das ansonsten intern ist. Je besser der Status Ihres Programms gekapselt ist, desto besser.Je einfacher es ist, Ihren Programm-Thread sicher zu machen und den Betreuern dabei zu helfen, dies auch so zu halten.

Beim Entwerfen threadsicherer Klassen sind gute objektorientierte technische Lösungen: Kapselung, VerÀnderlichkeit und eine klare Spezifikation von Invarianten Ihre Assistenten.

Wenn gute objektorientierte technische Designlösungen von den Anforderungen des Entwicklers abweichen, sollten Sie die Regeln fĂŒr gutes Design aus GrĂŒnden der Leistung oder der AbwĂ€rtskompatibilitĂ€t mit Legacy-Code opfern. Manchmal stehen Abstraktion und Kapselung im Widerspruch zur Leistung - obwohl nicht so oft, wie viele Entwickler denken -, aber die beste Vorgehensweise besteht darin, den Code zuerst richtig und dann schnell zu machen. Versuchen Sie, die Optimierung nur dann zu verwenden, wenn Messungen der ProduktivitĂ€t und der Anforderungen ergeben, dass Sie dies tun mĂŒssen (2) .

(2)Im Wettbewerbscode sollten Sie diese Praxis noch mehr als ĂŒblich einhalten. Da Wettbewerbsfehler Ă€ußerst schwer zu reproduzieren und nicht einfach zu debuggen sind, kann der Vorteil eines geringen Leistungsgewinns bei einigen selten verwendeten Codezweigen im Vergleich zu dem Risiko, dass das Programm unter Betriebsbedingungen abstĂŒrzt, vernachlĂ€ssigbar sein.

Wenn Sie entscheiden, dass Sie die Kapselung aufheben mĂŒssen, geht nicht alles verloren. Ihr Programm kann immer noch threadsicher gemacht werden, aber der Prozess wird komplizierter und teurer und das Ergebnis ist unzuverlĂ€ssig. Kapitel 4 beschreibt die Bedingungen, unter denen die Kapselung von Zustandsvariablen sicher verringert werden kann.

Bisher haben wir die Begriffe "thread safe class" und "thread safe program" fast austauschbar verwendet. Ist ein thread-sicheres Programm vollstÀndig aus thread-sicheren Klassen aufgebaut? Optional: Ein Programm, das vollstÀndig aus threadsicheren Klassen besteht, ist möglicherweise nicht threadsicher, und ein threadsicheres Programm enthÀlt möglicherweise Klassen, die nicht threadsicher sind. Probleme im Zusammenhang mit dem Layout threadsicherer Klassen werden auch in Kapitel 4 behandelt. In jedem Fall ist das Konzept einer threadsicheren Klasse nur dann sinnvoll, wenn die Klasse ihren eigenen Status kapselt. Der Begriff "Thread-Sicherheit" kann auf den Code angewendet werden, spricht jedoch vom Status und kann nur auf das Code-Array angewendet werden, das seinen Status kapselt (es kann sich um ein Objekt oder das gesamte Programm handeln).

2.1. Was ist Gewindesicherheit?


Das Definieren der Gewindesicherheit ist nicht einfach. Eine schnelle Google-Suche bietet Ihnen zahlreiche Optionen wie diese:

... kann von mehreren Programm-Threads ohne unerwĂŒnschte Interaktionen zwischen Threads aufgerufen werden.

... kann von zwei oder mehr Threads gleichzeitig aufgerufen werden, ohne dass der Aufrufer eine andere Aktion ausfĂŒhren muss.

Angesichts solcher Definitionen ist es nicht verwunderlich, dass wir die Thread-Sicherheit verwirrend finden! Wie kann man eine thread-sichere Klasse von einer unsicheren Klasse unterscheiden? Was meinen wir mit dem Wort "sicher"?

Im Zentrum jeder vernĂŒnftigen Definition der Gewindesicherheit steht der Begriff der Korrektheit.

Korrektheit bedeutet, dass die Klasse ihrer Spezifikation entspricht. Die Spezifikation definiert Invarianten, die den Zustand eines Objekts begrenzen, und Nachbedingungen, die die Auswirkungen von Operationen beschreiben. Woher wissen Sie, dass die Spezifikationen fĂŒr Klassen korrekt sind? Auf keinen Fall, aber dies hindert uns nicht daran, sie zu verwenden, nachdem wir uns davon ĂŒberzeugt haben, dass der Code funktioniert. Nehmen wir also an, dass Single-Threaded-Korrektheit sichtbar ist. Jetzt können wir davon ausgehen, dass sich die thread-sichere Klasse beim Zugriff von mehreren Threads korrekt verhĂ€lt.

Eine Klasse ist threadsicher, wenn sie sich beim Zugriff von mehreren Threads korrekt verhÀlt, unabhÀngig davon, wie diese Threads von der Arbeitsumgebung geplant oder verschachtelt werden, und ohne zusÀtzliche Synchronisation oder andere Koordination seitens des aufrufenden Codes.

Ein Multithread-Programm kann nicht threadsicher sein, wenn es selbst in einer Single-Thread-Umgebung nicht korrekt ist (3) . Wenn das Objekt korrekt implementiert ist, sollte keine Abfolge von Operationen - Zugriff auf öffentliche Methoden und Lesen oder Schreiben in öffentliche Felder - seine Invarianten oder Nachbedingungen verletzen. Keine Reihe von Operationen, die sequentiell oder wettbewerbsfĂ€hig fĂŒr Instanzen einer thread-sicheren Klasse ausgefĂŒhrt werden, kann dazu fĂŒhren, dass sich eine Instanz in einem ungĂŒltigen Zustand befindet.

(3) Wenn Sie hier die lose Verwendung des Begriffs Korrektheit stört, können Sie sich eine thread-sichere Klasse als eine Klasse vorstellen, die sowohl in einer Wettbewerbsumgebung als auch in einer Single-Thread-Umgebung fehlerhaft ist.

Thread-sichere Klassen kapseln alle erforderlichen Synchronisierungen selbst und benötigen nicht die Hilfe eines Clients.

2.1.1. Beispiel: Servlet ohne interne StatusunterstĂŒtzung


In Kapitel 1 haben wir die Strukturen aufgelistet, die Threads erstellen und daraus Komponenten aufrufen, fĂŒr deren Thread-Sicherheit Sie verantwortlich sind. Jetzt beabsichtigen wir, einen Servlet-Faktorisierungsdienst zu entwickeln und dessen FunktionalitĂ€t schrittweise zu erweitern, wĂ€hrend die Thread-Sicherheit erhalten bleibt.

Listing 2.1 zeigt ein einfaches Servlet, das eine Zahl aus einer Abfrage dekomprimiert, faktorisiert und die Ergebnisse als Antwort umschließt.

Listing 2.1. Servlet ohne interne ZustandsunterstĂŒtzung

@ThreadSafe
public class StatelessFactorizer implements Servlet {
      public void service(ServletRequest req, ServletResponse resp) {
            BigInteger i = extractFromRequest(req);
            BigInteger[] factors = factor(i);
            encodeIntoResponse(resp, factors);
      }
}

Die StatelessFactorizer-Klasse hat wie die meisten Servlets keinen internen Status: Sie enthĂ€lt keine Felder und verweist nicht auf Felder aus anderen Klassen. Der Status fĂŒr eine bestimmte Berechnung ist nur in lokalen Variablen vorhanden, die im Stream-Stack gespeichert sind und nur dem ausfĂŒhrenden Stream zur VerfĂŒgung stehen. Ein Thread, der auf StatelessFactorizer zugreift, kann das Ergebnis eines anderen Threads nicht beeinflussen, da diese Threads keinen gemeinsamen Status haben.

Objekte ohne interne StatusunterstĂŒtzung sind immer threadsicher.

Die Tatsache, dass die meisten Servlets ohne interne StatusunterstĂŒtzung implementiert werden können, reduziert die Belastung durch Threading der Servlets selbst erheblich. Und nur wenn Servlets sich an etwas erinnern mĂŒssen, steigen die Anforderungen an ihre Gewindesicherheit.

2.2. AtomizitÀt


Was passiert, wenn ein Statuselement ohne interne StatusunterstĂŒtzung zu einem Objekt hinzugefĂŒgt wird? Angenommen, wir möchten einen TrefferzĂ€hler hinzufĂŒgen, der die Anzahl der verarbeiteten Anforderungen misst. Sie können dem Servlet ein Feld vom Typ long hinzufĂŒgen und es bei jeder Anforderung erhöhen, wie in UnsafeCountingFactorizer in Listing 2.2 gezeigt.

Listing 2.2. Ein Servlet, das Anforderungen ohne die erforderliche Synchronisierung zÀhlt. Dies sollte nicht getan werden.

Bild

@NotThreadSafe
public class UnsafeCountingFactorizer implements Servlet {
      private long count = 0;

      public long getCount() { return count; }

      public void service(ServletRequest req, ServletResponse resp) {
            BigInteger i = extractFromRequest(req);
            BigInteger[] factors = factor(i);
            ++count;
            encodeIntoResponse(resp, factors);
      }
}

Leider ist die UnsafeCountingFactorizer-Klasse nicht threadsicher, selbst wenn sie in einer Single-Thread-Umgebung einwandfrei funktioniert. Wie UnsafeSequence ist es anfĂ€llig fĂŒr verlorene Updates. Obwohl die Anzahl der Inkrementoperationen ++ eine kompakte Syntax hat, ist sie nicht atomar, dh unteilbar, sondern eine Folge von drei Operationen: Liefern des aktuellen Werts, HinzufĂŒgen eines Werts und ZurĂŒckschreiben des neuen Werts. Bei den Operationen "Lesen, Ändern, Schreiben" wird der resultierende Zustand vom vorherigen abgeleitet.

In Abb. 1.1 Es wird gezeigt, was passieren kann, wenn zwei Threads gleichzeitig versuchen, den ZÀhler ohne Synchronisation zu erhöhen. Wenn der ZÀhler 9 ist, sehen beide Threads aufgrund einer erfolglosen Zeitkoordination den Wert 9, addieren einen und setzen den Wert auf 10. Der TrefferzÀhler beginnt also um eins zu verzögern.

Sie könnten denken, dass ein leicht ungenauer TrefferzĂ€hler in einem Webdienst ein akzeptabler Verlust ist, und manchmal auch. Wenn der ZĂ€hler jedoch zum Erstellen von Sequenzen oder eindeutigen Kennungen von Objekten verwendet wird, kann die RĂŒckgabe des gleichen Werts aus mehreren Aktivierungen zu schwerwiegenden DatenintegritĂ€tsproblemen fĂŒhren. Die Möglichkeit des Auftretens falscher Ergebnisse aufgrund einer erfolglosen zeitlichen Koordination ergibt sich unter Rennbedingungen.

2.2.1. Rennbedingungen


Die UnsafeCountingFactorizer-Klasse hat mehrere Rennbedingungen (4) . Die hĂ€ufigste Art von Rennbedingung ist die Situation „PrĂŒfen und dann handeln“, in der anhand einer möglicherweise veralteten Beobachtung entschieden wird, was als nĂ€chstes zu tun ist.

(4) (data race). , . , , , , , . Java. , , . UnsafeCountingFactorizer . 16.

Im wirklichen Leben begegnen wir oft einer Rennbedingung. Angenommen, Sie planen, mittags einen Freund im Starbucks CafĂ© am Universitetskiy Prospekt zu treffen. Aber Sie werden feststellen, dass es zwei Starbucks auf der University Avenue gibt. Um 12:10 Uhr sehen Sie Ihren Freund nicht in CafĂ© A und gehen in CafĂ© B, aber er ist auch nicht da. Entweder ist Ihr Freund zu spĂ€t oder er ist unmittelbar nach Ihrer Abreise im CafĂ© A angekommen, oder er war im CafĂ© B, hat Sie aber gesucht und ist jetzt auf dem Weg zum CafĂ© A. Wir werden das letztere akzeptieren, das heißt, den schlimmsten Fall. Jetzt, 12:15 Uhr, und Sie fragen sich beide, ob Ihr Freund sein Versprechen gehalten hat. Wirst du in ein anderes CafĂ© zurĂŒckkehren? Wie oft wirst du hin und her gehen? Wenn Sie sich nicht auf ein Protokoll geeinigt haben, können Sie den ganzen Tag in koffeinhaltiger Euphorie die University Avenue entlang spazieren.
Das Problem beim Ansatz „Gehen Sie spazieren und sehen Sie, ob er da ist“ besteht darin, dass ein Spaziergang entlang der Straße zwischen zwei CafĂ©s mehrere Minuten dauert und sich wĂ€hrend dieser Zeit der Zustand des Systems Ă€ndern kann.

Das Beispiel mit Starbucks zeigt die AbhĂ€ngigkeit des Ergebnisses von der relativen zeitlichen Koordination der Ereignisse (davon, wie lange Sie in einem CafĂ© auf einen Freund warten usw.). Die Beobachtung, dass er nicht in CafĂ© A ist, wird möglicherweise ungĂŒltig: Sobald Sie die VordertĂŒr verlassen, kann er durch die HintertĂŒr eintreten. Die meisten Rennbedingungen verursachen Probleme wie eine unerwartete Ausnahme, ĂŒberschriebene Daten und DateibeschĂ€digung.

2.2.2. Beispiel: Rennbedingungen bei verzögerter Initialisierung


Ein hĂ€ufiger Trick bei der Verwendung des Ansatzes „PrĂŒfen und dann handeln“ ist die verzögerte Initialisierung (LazyInitRace). Ziel ist es, die Initialisierung des Objekts zu verschieben, bis es benötigt wird, und sicherzustellen, dass es nur einmal initialisiert wird. In Listing 2.3 stellt die Methode getInstance sicher, dass das ExpensiveObject initialisiert wird und eine vorhandene Instanz zurĂŒckgibt, oder erstellt auf andere Weise eine neue Instanz und gibt sie zurĂŒck, nachdem ein Verweis darauf beibehalten wurde.

Listing 2.3. Die Rennbedingung ist in einer trÀgen Initialisierung. Dies sollte nicht getan werden.

Bild

@NotThreadSafe
public class LazyInitRace {
      private ExpensiveObject instance = null;

      public ExpensiveObject getInstance() {
            if (instance == null)
                instance = new ExpensiveObject();
            return instance;
      }
}

Die LazyInitRace-Klasse enthĂ€lt Rennbedingungen. Angenommen, die Threads A und B fĂŒhren gleichzeitig die Methode getInstance aus. A erkennt, dass das Instanzfeld null ist, und erstellt ein neues ExpensiveObject. Thread B prĂŒft auch, ob das Instanzfeld dieselbe Null ist. Das Vorhandensein von Null im Feld zu diesem Zeitpunkt hĂ€ngt von der Zeitkoordination ab, einschließlich der Planungsschwankungen und der Zeit, die erforderlich ist, um eine Instanz des ExpensiveObject zu erstellen und den Wert im Instanzfeld festzulegen. Wenn das Instanzfeld bei der ÜberprĂŒfung durch B null ist, können zwei Codeelemente, die die Methode getInstance aufrufen, zwei unterschiedliche Ergebnisse erzielen, selbst wenn die Methode getInstance immer dieselbe Instanz zurĂŒckgeben soll.

Der TrefferzĂ€hler in UnsafeCountingFactorizer enthĂ€lt auch die Rennbedingungen. Der Ansatz "Lesen, Ändern, Schreiben" impliziert, dass der Stream zum Erhöhen des ZĂ€hlers seinen vorherigen Wert kennen und sicherstellen muss, dass niemand diesen Wert wĂ€hrend des Aktualisierungsprozesses Ă€ndert oder verwendet.

Wie die meisten Wettbewerbsfehler fĂŒhren die Rennbedingungen nicht immer zum Scheitern: Die vorĂŒbergehende Koordination ist erfolgreich. Wenn die LazyInitRace-Klasse jedoch zum Instanziieren der Registrierung der gesamten Anwendung verwendet wird, gehen Registrierungen verloren, wenn verschiedene Instanzen aus mehreren Aktivierungen zurĂŒckgegeben werden, oder Aktionen erhalten widersprĂŒchliche Darstellungen der Gruppe registrierter Objekte. Wenn die UnsafeSequence-Klasse zum Generieren von EntitĂ€tskennungen in einer Datenerhaltungsstruktur verwendet wird, können zwei verschiedene Objekte dieselbe Kennung haben, wodurch IdentitĂ€tsbeschrĂ€nkungen verletzt werden.

2.2.3. Zusammengesetzte Aktionen


Sowohl LazyInitRace als auch UnsafeCountingFactorizer enthalten eine Folge von Operationen, die atomar sein mĂŒssen. Um jedoch eine Race-Bedingung zu verhindern, muss es fĂŒr andere Threads ein Hindernis geben, die Variable zu verwenden, wĂ€hrend ein Thread sie Ă€ndert.

Operationen A und B sind atomar, wenn aus Sicht des Threads, der Operation A ausfĂŒhrt, Operation B entweder vollstĂ€ndig von einem anderen Thread ausgefĂŒhrt oder nicht einmal teilweise ausgefĂŒhrt wurde.

Die AtomizitĂ€t der Inkrementoperation in UnsafeSequence wĂŒrde die in Abb. 1 gezeigte Racebedingung vermeiden. 1.1. Die Operationen "prĂŒfen und dann handeln" und "lesen, Ă€ndern, schreiben" sollten immer atomar sein. Sie werden zusammengesetzte Aktionen genannt - Abfolgen von Operationen, die atomar ausgefĂŒhrt werden mĂŒssen, um threadsicher zu bleiben. Im nĂ€chsten Abschnitt werden wir uns mit dem Sperren befassen - einem in Java integrierten Mechanismus, der AtomizitĂ€t bietet. In der Zwischenzeit werden wir das Problem auf andere Weise beheben, indem wir die vorhandene thread-sichere Klasse anwenden, wie im Countingfactorizer in Listing 2.4 gezeigt.

Listing 2.4. Servlet-ZĂ€hlanforderungen mit AtomicLong

@ThreadSafe
public class CountingFactorizer implements Servlet {
      private final AtomicLong count = new AtomicLong(0);

      public long getCount() { return count.get(); }

      public void service(ServletRequest req, ServletResponse resp) {
            BigInteger i = extractFromRequest(req);
            BigInteger[] factors = factor(i);
            count.incrementAndGet();
            encodeIntoResponse(resp, factors);
      }
}

Das Paket java.util.concurrent.atomic enthÀlt atomare Variablen zum Verwalten von KlassenzustÀnden. Durch Ersetzen des ZÀhlertyps von long auf AtomicLong garantieren wir, dass alle Aktionen, die sich auf den Status des ZÀhlers beziehen, atomar1 sind. Da der Status des Servlets der Status des ZÀhlers ist und der ZÀhler threadsicher ist, wird unser Servlet threadsicher.

Wenn ein einzelnes Statuselement zu einer Klasse hinzugefĂŒgt wird, die den internen Status nicht unterstĂŒtzt, ist die resultierende Klasse threadsicher, wenn der Status vollstĂ€ndig vom threadsicheren Objekt gesteuert wird. Wie wir im nĂ€chsten Abschnitt sehen werden, ist der Übergang von einer Zustandsvariablen zur nĂ€chsten nicht so einfach wie der Übergang von Null zu Eins.

Verwenden Sie gegebenenfalls vorhandene thread-sichere Objekte wie AtomicLong, um den Status Ihrer Klasse zu steuern. Mögliche ZustĂ€nde vorhandener thread-sicherer Objekte und deren ÜbergĂ€nge in andere ZustĂ€nde sind einfacher zu pflegen und auf Thread-Sicherheit zu prĂŒfen als beliebige Zustandsvariablen.

»Weitere Informationen zum Buch finden Sie auf der Website des Herausgebers.
» Inhalt
» Auszug

fĂŒr Khabrozhiteley 25% Rabatt auf den Gutschein - Java

Nach Zahlung der Papierversion des Buches wird ein elektronisches Buch per E-Mail verschickt.

All Articles