Galileo Computing < openbook >
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel (2. Aufl.) von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Java ist auch eine Insel (2. Auflage)
gp Kapitel 9 Threads und nebenläufige Programmierung
  gp 9.1 Prozesse und Threads
  gp 9.2 Threads erzeugen
    gp 9.2.1 Threads über die Schnittstelle Runnable implementieren
    gp 9.2.2 Threads über Runnable starten
    gp 9.2.3 Die Klasse Thread erweitern
    gp 9.2.4 Erweitern von Thread oder implementieren von Runnable?
  gp 9.3 Threads schlafen
    gp 9.3.1 Eine Zeituhr
  gp 9.4 Die Klassen Timer und TimerTask
  gp 9.5 Die Zustände eines Threads
    gp 9.5.1 Das Ende eines Threads
    gp 9.5.2 Einen Thread höflich mit Interrupt beenden
    gp 9.5.3 Der stop() von außen
    gp 9.5.4 Das ThreadDeath-Objekt
    gp 9.5.5 Auf das Ende warten mit join()
  gp 9.6 Arbeit niederlegen und wieder aufnehmen
  gp 9.7 Priorität
    gp 9.7.1 Threads hoher Priorität und das AWT
    gp 9.7.2 Granularität und Vorrang
  gp 9.8 Dämonen (engl. Daemon)
  gp 9.9 Kooperative und nicht kooperative Threads
  gp 9.10 Synchronisation über kritische Abschnitte
    gp 9.10.1 Gemeinsam genutzte Daten
    gp 9.10.2 Probleme beim gemeinsamen Zugriff und kritische Abschnitte
    gp 9.10.3 Punkte parallel initialisieren
    gp 9.10.4 i++ sieht atomar aus, ist es aber nicht
    gp 9.10.5 Abschnitte mit synchronized schützen
    gp 9.10.6 Monitore
    gp 9.10.7 Synchronized-Methode am Beispiel der Klasse StringBuffer
    gp 9.10.8 Synchronisierte Blöcke
    gp 9.10.9 Vor- und Nachteile von synchronisierten Blöcken und Methoden
    gp 9.10.10 Nachträglich synchronisieren
    gp 9.10.11 Monitore sind reentrant, gut für die Geschwindigkeit
    gp 9.10.12 Deadlocks
  gp 9.11 Variablen mit volatile kennzeichnen
  gp 9.12 Synchronisation über Warten und Benachrichtigen
    gp 9.12.1 Warten mit wait() und Aufwecken mit notify()
    gp 9.12.2 Mehrere Wartende und notifyAll()
    gp 9.12.3 wait() mit einer Zeitspanne
    gp 9.12.4 Beispiel Erzeuger-Verbraucher-Programm
    gp 9.12.5 Semaphoren
  gp 9.13 Grenzen von Threads
  gp 9.14 Aktive Threads in der Umgebung
  gp 9.15 Gruppen von Threads in einer Thread-Gruppe
    gp 9.15.1 Etwas über die aktuelle Thread-Gruppe herausfinden
    gp 9.15.2 Threads in einer Thread-Gruppe anlegen
    gp 9.15.3 Methoden von Thread und ThreadGroup im Vergleich
  gp 9.16 Einen Abbruch der virtuellen Maschine erkennen


Galileo Computing

9.11 Variablen mit volatile kennzeichnen  toptop

Die JVM arbeitet bei den Ganzzahl-Datentypen kleiner gleich int intern nur mit einem int. Doch obwohl es auch für die Datentypen, die größer als 32 Bit sind, also long und double, einzelne Bytecode-Befehle gibt, ist nicht gesichert, dass die Operationen auf diesen Datentypen unteilbar sind. Es kann also passieren, dass ein Thread mitten in einer long- oder double-Operation von einem anderen Thread verdrängt wird. Greifen zwei Threads auf die gleiche lange Variable zu, so könnte es passieren, dass der eine Thread eine Hälfte schreibt und der andere Thread die andere.

Beispiel   Eine long-Variable l ist mit dem Wert 0x12345678 90abcdefL initialisiert. Aus Gründen der Übersichtlichkeit ist in der ersten Hälfte des long-Werts ein Leerzeichen eingefügt. Jetzt versuchen zwei Threads gleichzeitig die Variable zu ändern.
l = 0x00000000 00000000L;             l = 0xabcdef12 34567890L;

Wenn hier der erste Thread den ersten Teil der long-Variablen mit 00000000 beschreibt, und dann ein Kontextwechsel stattfindet, ist die Variable fälschlicherweise mit 00000000 90abcdefL initialisiert. Kommt nun nach dem Kontextwechsel der zweite Thread, so kann dieser vielleicht auch nur den ersten Teil der Variable mit abcdef12 beschreiben, sodass danach die Variable den völlig krummen Wert abcdef12 90abcdefL enthält.


for ( int j = 0; j <= 1000; j++ )
  i = j;

Eine optimierende Laufzeitumgebung könnte nun auf die Idee kommen, nicht bei jedem Schleifendurchlauf die Objektvariable zu erhöhen – denn das kostet zusätzliche Laufzeit –, sondern nur zu Beginn und am Ende der Schleife. Das heißt, es gilt lediglich eine Wertzuweisung zu machen, also i mit dem Wert 1000 zu belegen. Doch wenn der Prozessor Berechnungen im internen Speicher vornimmt und das Ergebnis nicht immer in den Variablen aktualisiert, kommt es zu schwer kontrollierbaren Nebeneffekten, denn womöglich wundern sich andere Threads über die abrupten Inkrements.

Auch hier hilft wieder das besondere Schlüsselwort. Ist die Variable mit volatile gekennzeichnet, wird das Ergebnis nicht im Zwischenspeicher belassen, sondern immer aktualisiert. Die parallelen Threads sehen somit immer den korrekten Variablenwert, da er vor jeder Benutzung aus dem Speicher gelesen und nach einer Änderung sofort wieder zurückgeschrieben wird.

Einsatz von volatile

Das Schlüsselwort volatile findet sich im Quellcode sehr selten und in der ersten Java-Beta-Version wurde es überhaupt nicht benutzt. Wenn wir eine andere Lösung suchen, dann können wir noch zu den Wrapper-Klassen greifen. Exemplare dieser Klassen haben im Gegensatz zu den elementaren Datentypen einen Monitor, der sich für die Synchronisation eignet. Für das long- und double-Problem ist das die einzige Lösung.

Beispiel   Eine Laufzeitumgebung könnte den Zugriff auf Objektvariablen optimieren, indem zuerst die Variable in eine interne lokale Variable kopiert und anschließend das Ergebnis zurückgespeichert wird. Ist i eine Objektvariable, dann könnte folgende Schleife verbessert werden:
for ( int cnt = 0; cnt < 1000000; cnt++ )
  i++;

Wenn die Umgebung zuerst i in eine lokale Variable kopiert, dann die gesamte Schleife ausführt und erst anschließend den Wert nach i zurückkopiert, haben wir viel Zeit gespart, denn der Zugriff auf eine lokale Variable, die im Register des Prozessors leben kann, ist wesentlich schneller als der Zugriff auf eine Objektvariable. Dies ist im Übrigen eine beliebte Strategie, um die Performance eines Programms zu steigern. Sie nutzt das Muster der Variablenzugriffe in dem Programmstück aus. Doch dadurch sind Änderungen am Wert von i nicht sofort für andere Threads sichtbar, denen damit die Information über die einzelnen Inkrementschritte fehlt.


Listing 9.14   VolatileDemo.java

class VolatileDemo extends Thread
{
  /* volatile */ int i;

  public void run()
  {
    while ( true )
    {
      for ( int cnt = 0; cnt < 1000000; cnt++ )
        i++;
    }
  }

  public static void main( String args[] )
    throws InterruptedException
  {
    VolatileDemo t = new VolatileDemo();
    t.start();

    while ( true )
    {
      Thread.sleep( 1000 );
      System.out.println( t.i );
    }
  }
}




Copyright © Galileo Press GmbH 2003
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de