Wie die virtuelle Java-Maschine die Thread-Synchronisation durchführt

Alle Java-Programme werden in Klassendateien kompiliert, die Bytecodes enthalten, die Maschinensprache der virtuellen Java-Maschine. In diesem Artikel wird erläutert, wie die Thread-Synchronisierung von der virtuellen Java-Maschine ausgeführt wird, einschließlich der relevanten Bytecodes. (1.750 Wörter)

In diesem Monat befasst sich Under The Hood mit der Thread-Synchronisation sowohl in der Java-Sprache als auch in der Java Virtual Machine (JVM). Dieser Artikel ist der letzte in der langen Reihe von Bytecode-Artikeln, die ich letzten Sommer begonnen habe. Es beschreibt die einzigen zwei Opcodes, die in direktem Zusammenhang mit der Thread-Synchronisation stehen, die Opcodes, die zum Betreten und Verlassen von Monitoren verwendet werden.

Threads und gemeinsam genutzte Daten

Eine der Stärken der Programmiersprache Java ist die Unterstützung von Multithreading auf Sprachebene. Ein Großteil dieser Unterstützung konzentriert sich auf die Koordinierung des Zugriffs auf Daten, die von mehreren Threads gemeinsam genutzt werden.

Die JVM organisiert die Daten einer laufenden Java-Anwendung in mehrere Laufzeitdatenbereiche: einen oder mehrere Java-Stapel, einen Heap und einen Methodenbereich. Einen Hintergrund zu diesen Speicherbereichen finden Sie im ersten Artikel unter der Haube : "Die schlanke, gemeine virtuelle Maschine."

In der virtuellen Java-Maschine erhält jeder Thread einen Java-Stack , der Daten enthält, auf die kein anderer Thread zugreifen kann, einschließlich der lokalen Variablen, Parameter und Rückgabewerte jeder vom Thread aufgerufenen Methode. Die Daten auf dem Stapel sind auf primitive Typen und Objektreferenzen beschränkt. In der JVM ist es nicht möglich, das Bild eines tatsächlichen Objekts auf dem Stapel zu platzieren. Alle Objekte befinden sich auf dem Heap.

Es gibt nur einen Heap in der JVM, und alle Threads teilen ihn. Der Heap enthält nur Objekte. Es gibt keine Möglichkeit, einen einzelnen primitiven Typ oder eine Objektreferenz auf dem Haufen zu platzieren - diese Dinge müssen Teil eines Objekts sein. Arrays befinden sich auf dem Heap, einschließlich Arrays primitiver Typen. In Java sind Arrays jedoch auch Objekte.

Neben dem Java-Stack und dem Heap können sich die anderen Ortsdaten in der JVM im Methodenbereich befinden , der alle vom Programm verwendeten Klassenvariablen (oder statischen Variablen) enthält. Der Methodenbereich ähnelt dem Stapel, da er nur primitive Typen und Objektreferenzen enthält. Im Gegensatz zum Stack werden die Klassenvariablen im Methodenbereich jedoch von allen Threads gemeinsam genutzt.

Objekt- und Klassensperren

Wie oben beschrieben, enthalten zwei Speicherbereiche in der Java Virtual Machine Daten, die von allen Threads gemeinsam genutzt werden. Diese sind:

  • Der Heap, der alle Objekte enthält
  • Der Methodenbereich, der alle Klassenvariablen enthält

Wenn mehrere Threads dieselben Objekte oder Klassenvariablen gleichzeitig verwenden müssen, muss ihr Zugriff auf die Daten ordnungsgemäß verwaltet werden. Andernfalls hat das Programm ein unvorhersehbares Verhalten.

Um den Zugriff auf gemeinsam genutzte Daten zwischen mehreren Threads zu koordinieren, ordnet die virtuelle Java-Maschine jedem Objekt und jeder Klasse eine Sperre zu. Eine Sperre ist wie ein Privileg, das jeweils nur ein Thread "besitzen" kann. Wenn ein Thread ein bestimmtes Objekt oder eine bestimmte Klasse sperren möchte, fragt er die JVM. Irgendwann, nachdem der Thread die JVM um eine Sperre gebeten hat - vielleicht sehr bald, vielleicht später, möglicherweise nie -, gibt die JVM dem Thread die Sperre. Wenn der Thread die Sperre nicht mehr benötigt, gibt er sie an die JVM zurück. Wenn ein anderer Thread dieselbe Sperre angefordert hat, übergibt die JVM die Sperre an diesen Thread.

Klassensperren werden tatsächlich als Objektsperren implementiert. Wenn die JVM eine Klassendatei lädt, wird eine Klasseninstanz erstellt java.lang.Class. Wenn Sie eine Klasse sperren, sperren Sie tatsächlich das ClassObjekt dieser Klasse .

Threads müssen keine Sperre erhalten, um auf Instanz- oder Klassenvariablen zugreifen zu können. Wenn ein Thread jedoch eine Sperre erhält, kann kein anderer Thread auf die gesperrten Daten zugreifen, bis der Thread, dem die Sperre gehört, diese freigibt.

Monitore

Die JVM verwendet Sperren in Verbindung mit Monitoren . Ein Monitor ist im Grunde genommen ein Wächter, indem er eine Codesequenz überwacht und sicherstellt, dass jeweils nur ein Thread den Code ausführt.

Jeder Monitor ist einer Objektreferenz zugeordnet. Wenn ein Thread bei der ersten Anweisung in einem Codeblock ankommt, der sich unter dem wachsamen Auge eines Monitors befindet, muss der Thread eine Sperre für das referenzierte Objekt erhalten. Der Thread darf den Code erst ausführen, wenn er die Sperre erhalten hat. Sobald die Sperre erreicht wurde, tritt der Thread in den Block des geschützten Codes ein.

Wenn der Thread den Block verlässt, gibt er die Sperre für das zugehörige Objekt auf, unabhängig davon, wie er den Block verlässt.

Mehrere Schlösser

Ein einzelner Thread darf dasselbe Objekt mehrmals sperren. Für jedes Objekt zählt die JVM, wie oft das Objekt gesperrt wurde. Ein entsperrtes Objekt hat eine Zählung von Null. Wenn ein Thread die Sperre zum ersten Mal erhält, wird die Anzahl auf eins erhöht. Jedes Mal, wenn der Thread eine Sperre für dasselbe Objekt erhält, wird eine Anzahl erhöht. Jedes Mal, wenn der Thread die Sperre aufhebt, wird die Anzahl dekrementiert. Wenn die Anzahl Null erreicht, wird die Sperre aufgehoben und anderen Threads zur Verfügung gestellt.

Synchronisierte Blöcke

In der Java-Sprachterminologie wird die Koordination mehrerer Threads, die auf gemeinsam genutzte Daten zugreifen müssen, als Synchronisation bezeichnet . Die Sprache bietet zwei integrierte Möglichkeiten zum Synchronisieren des Zugriffs auf Daten: mit synchronisierten Anweisungen oder synchronisierten Methoden.

Synchronisierte Anweisungen

Um eine synchronisierte Anweisung zu erstellen, verwenden Sie das synchronizedSchlüsselwort mit einem Ausdruck, der wie in der folgenden reverseOrder()Methode zu einer Objektreferenz ausgewertet wird:

class KitchenSync { private int[] intArray = new int[10]; void reverseOrder() { synchronized (this) { int halfWay = intArray.length / 2; for (int i = 0; i < halfWay; ++i) { int upperIndex = intArray.length - 1 - i; int save = intArray[upperIndex]; intArray[upperIndex] = intArray[i]; intArray[i] = save; } } } }

Im obigen Fall werden die im synchronisierten Block enthaltenen Anweisungen erst ausgeführt, wenn eine Sperre für das aktuelle Objekt ( this) erworben wurde. Wenn thisder Ausdruck anstelle einer Referenz eine Referenz auf ein anderes Objekt ergibt, wird die diesem Objekt zugeordnete Sperre erfasst, bevor der Thread fortgesetzt wird.

Zwei Opcodes monitorenterund monitorexitwerden für Synchronisationsblöcke innerhalb von Methoden verwendet, wie in der folgenden Tabelle gezeigt.

Tabelle 1. Monitore

Opcode Operand (en) Beschreibung
monitorenter keiner pop objectref, erwerben Sie die mit objectref verknüpfte Sperre
monitorexit keiner pop objectref, geben Sie die mit objectref verknüpfte Sperre auf

Wenn monitorenterdie virtuelle Java-Maschine auf sie stößt, erhält sie die Sperre für das Objekt, auf das objectref auf dem Stapel verweist. Wenn der Thread bereits die Sperre für dieses Objekt besitzt, wird eine Anzahl erhöht. Jedes Mal, wenn monitorexitfür den Thread auf dem Objekt ausgeführt wird, wird die Anzahl dekrementiert. Wenn der Zähler Null erreicht, wird der Monitor freigegeben.

Sehen Sie sich die Bytecode-Sequenz an, die von der reverseOrder()Methode der KitchenSyncKlasse generiert wurde .

Note that a catch clause ensures the locked object will be unlocked even if an exception is thrown from within the synchronized block. No matter how the synchronized block is exited, the object lock acquired when the thread entered the block definitely will be released.

Synchronized methods

To synchronize an entire method, you just include the synchronized keyword as one of the method qualifiers, as in:

class HeatSync { private int[] intArray = new int[10]; synchronized void reverseOrder() { int halfWay = intArray.length / 2; for (int i = 0; i < halfWay; ++i) { int upperIndex = intArray.length - 1 - i; int save = intArray[upperIndex]; intArray[upperIndex] = intArray[i]; intArray[i] = save; } } }

The JVM does not use any special opcodes to invoke or return from synchronized methods. When the JVM resolves the symbolic reference to a method, it determines whether the method is synchronized. If it is, the JVM acquires a lock before invoking the method. For an instance method, the JVM acquires the lock associated with the object upon which the method is being invoked. For a class method, it acquires the lock associated with the class to which the method belongs. After a synchronized method completes, whether it completes by returning or by throwing an exception, the lock is released.

Coming next month

Now that I have gone through the entire bytecode instruction set, I will be broadening the scope of this column to include various aspects or applications of Java technology, not just the Java virtual machine. Next month, I'll begin a multi-part series that gives an in-depth overview of Java's security model.

Bill Venners schreibt seit 12 Jahren professionell Software. Er ist im Silicon Valley ansässig und bietet Software-Beratungs- und Schulungsdienste unter dem Namen Artima Software Company an. Im Laufe der Jahre hat er Software für die Unterhaltungselektronik-, Bildungs-, Halbleiter- und Lebensversicherungsbranche entwickelt. Er hat in vielen Sprachen auf vielen Plattformen programmiert: Assemblersprache auf verschiedenen Mikroprozessoren, C unter Unix, C ++ unter Windows, Java im Web. Er ist Autor des Buches: Inside the Java Virtual Machine, veröffentlicht von McGraw-Hill.

Erfahren Sie mehr über dieses Thema

  • The book The Java virtual machine Specification (//www.aw.com/cp/lindholm-yellin.html), by Tim Lindholm and Frank Yellin (ISBN 0-201-63452-X), part of The Java Series (//www.aw.com/cp/javaseries.html), from Addison-Wesley, is the definitive Java virtual machine reference.
  • Previous "Under The Hood" articles:
  • "The Lean, Mean Virtual Machine" Gives an introduction to the Java virtual machine.
  • "The Java Class File Lifestyle" Gives an overview to the Java class file, the file format into which all Java programs are compiled.
  • "Java's Garbage-Collected Heap" Gives an overview of garbage collection in general and the garbage-collected heap of the Java virtual machine in particular.
  • "Bytecode Basics" Introduces the bytecodes of the Java virtual machine, and discusses primitive types, conversion operations, and stack operations in particular.
  • "Floating Point Arithmetic" Describes the Java virtual machine's floating-point support and the bytecodes that perform floating point operations.
  • "Logic and Arithmetic" Describes the Java virtual machine's support for logical and integer arithmetic, and the related bytecodes.
  • "Objects and Arrays" Describes how the Java virtual machine deals with objects and arrays, and discusses the relevant bytecodes.
  • "Exceptions" Describes how the Java virtual machine deals with exceptions, and discusses the relevant bytecodes.
  • "Try-finally" Beschreibt, wie die virtuelle Java-Maschine try-finally-Klauseln implementiert, und erläutert die relevanten Bytecodes.
  • "Kontrollfluss" Beschreibt, wie die Java Virtual Machine den Kontrollfluss implementiert, und erläutert die relevanten Bytecodes.
  • "Die Architektur von Aglets" Beschreibt das Innenleben von Aglets, der autonomen Java-basierten Software-Agententechnologie von IBM.
  • "The Point of Aglets" Analysiert den realen Nutzen mobiler Agenten wie Aglets, der autonomen Java-basierten Software-Agententechnologie von IBM.
  • "Methodenaufruf und -rückgabe" Erläutert, wie die Java Virtual Machine Methoden aufruft und von diesen zurückgibt, einschließlich der relevanten Bytecodes.

Diese Geschichte "Wie die virtuelle Java-Maschine die Thread-Synchronisation durchführt" wurde ursprünglich von JavaWorld veröffentlicht.