Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Mathematisches
6 Eigene Klassen schreiben
7 Angewandte Objektorientierung
8 Exceptions
9 Generics, innere Klassen
10 Die Klassenbibliothek
11 Threads und nebenläufige Programmierung
12 Datenstrukturen und Algorithmen
13 Raum und Zeit
14 Dateien und Datenströme
15 Die eXtensible Markup Language (XML)
16 Grafische Oberflächen mit Swing
17 Grafikprogrammierung
18 Netzwerkprogrammierung
19 Verteilte Programmierung mit RMI und Web–Services
20 JavaServer Pages und Servlets
21 Applets
22 Midlets und die Java ME
23 Datenbankmanagement mit JDBC
24 Reflection und Annotationen
25 Logging und Monitoring
26 Sicherheitskonzepte
27 Java Native Interface (JNI)
28 Dienstprogramme für die Java-Umgebung
Stichwort
Download: - ZIP, ca. 14,1 MB
Buch bestellen
Ihre Meinung?
<< zurück | Galileo Computing / <openbook> / Java-Insel | vor >>
Java ist auch eine Insel (8. Auflage) von Christian Ullenboom Programmieren mit der Java Standard Edition Version 6
Java ist auch eine Insel (8. Aufl.) 8., aktual. Auflage, geb., mit DVD 1.475 S., 49,90 Euro Galileo Computing ISBN 978-3-8362-1371-4 |
| 11 Threads und nebenläufige Programmierung
|  | 11.1 Nebenläufigkeit
|  |  | 11.1.1 Threads und Prozesse
|  |  | 11.1.2 Wie parallele Programme die Geschwindigkeit steigern können
|  |  | 11.1.3 Was Java für Nebenläufigkeit alles bietet
|  | 11.2 Threads erzeugen
|  |  | 11.2.1 Threads über die Schnittstelle Runnable implementieren
|  |  | 11.2.2 Thread mit Runnable starten
|  |  | 11.2.3 Der Name eines Threads
|  |  | 11.2.4 Die Klasse Thread erweitern
|  |  | 11.2.5 Wer bin ich?
|  | 11.3 Die Zustände eines Threads
|  |  | 11.3.1 Threads schlafen
|  |  | 11.3.2 Mit yield() auf Rechenzeit verzichten
|  |  | 11.3.3 Das Ende eines Threads
|  |  | 11.3.4 UncaughtExceptionHandler für unbehandelte Ausnahmen
|  |  | 11.3.5 Einen Thread höflich mit Interrupt beenden
|  |  | 11.3.6 Der stop() von außen und die Rettung mit ThreadDeath
|  |  | 11.3.7 Ein Rendezvous mit join()
|  |  | 11.3.8 Barrier und Austausch mit Exchanger
|  |  | 11.3.9 Arbeit niederlegen und wieder aufnehmen
|  |  | 11.3.10 Priorität
|  |  | 11.3.11 Der Thread als Dämon
|  | 11.4 Der Ausführer (Executor) kommt
|  |  | 11.4.1 Die Schnittstelle Executor
|  |  | 11.4.2 Die Thread-Pools
|  |  | 11.4.3 Threads mit Rückgabe über Callable
|  |  | 11.4.4 Mehrere Callable abarbeiten
|  |  | 11.4.5 Mit ScheduledExecutorService wiederholende Ausgaben und Zeitsteuerungen
|  | 11.5 Synchronisation über kritische Abschnitte
|  |  | 11.5.1 Gemeinsam genutzte Daten
|  |  | 11.5.2 Probleme beim gemeinsamen Zugriff und kritische Abschnitte
|  |  | 11.5.3 Punkte parallel initialisieren
|  |  | 11.5.4 i++ sieht atomar aus, ist es aber nicht
|  |  | 11.5.5 Kritische Abschnitte schützen
|  |  | 11.5.6 Schützen mit ReentrantLock
|  |  | 11.5.7 Synchronisieren mit synchronized
|  |  | 11.5.8 Synchronized-Methoden der Klasse StringBuffer
|  |  | 11.5.9 Mit synchronized synchronisierte Blöcke
|  |  | 11.5.10 Dann machen wir doch gleich alles synchronisiert!
|  |  | 11.5.11 Lock-Freigabe im Fall von Exceptions
|  |  | 11.5.12 Mit synchronized nachträglich synchronisieren
|  |  | 11.5.13 Monitore sind reentrant – gut für die Geschwindigkeit
|  |  | 11.5.14 Synchronisierte Methodenaufrufe zusammenfassen
|  |  | 11.5.15 Deadlocks
|  | 11.6 Synchronisation über Warten und Benachrichtigen
|  |  | 11.6.1 Die Schnittstelle Condition
|  |  | 11.6.2 It’s Disco-Time
|  |  | 11.6.3 Warten mit wait() und Aufwecken mit notify()
|  |  | 11.6.4 Falls der Lock fehlt: IllegalMonitorStateException
|  |  | 11.6.5 Semaphor
|  | 11.7 Atomare Operationen und frische Werte mit volatile
|  |  | 11.7.1 Der Modifizierer volatile bei Objekt-/Klassenvariablen
|  |  | 11.7.2 Das Paket java.util.concurrent.atomic
|  | 11.8 Mit dem Thread verbundene Variablen
|  |  | 11.8.1 ThreadLocal
|  |  | 11.8.2 InheritableThreadLocal
|  | 11.9 Gruppen von Threads in einer Thread-Gruppe
|  |  | 11.9.1 Aktive Threads in der Umgebung
|  |  | 11.9.2 Etwas über die aktuelle Thread-Gruppe herausfinden
|  |  | 11.9.3 Threads in einer Thread-Gruppe anlegen
|  |  | 11.9.4 Methoden von Thread und ThreadGroup im Vergleich
|  | 11.10 Zeitgesteuerte Abläufe
|  |  | 11.10.1 Die Klassen Timer und TimerTask
|  |  | 11.10.2 Job-Scheduler Quartz
|  | 11.11 Einen Abbruch der virtuellen Maschine erkennen
|  | 11.12 Zum Weiterlesen
Just Be. – Calvin Klein # 11 Threads und nebenläufige Programmierung ## 11.1 Nebenläufigkeit Moderne Betriebssysteme geben dem Benutzer die Illusion, dass verschiedene Programme gleichzeitig ausgeführt werden – die Betriebssysteme nennen sich multitaskingfähig. Was wir dann wahrnehmen, ist eine Quasiparallelität, die im Deutschen auch »Nebenläufigkeit« [Mitunter sind die Begriffe »parallel« und »nebenläufig« nicht äquivalent definiert. Wir wollen sie in diesem Zusammenhang aber synonym benutzen.] genannt wird. Diese Nebenläufigkeit der Programme wird durch das Betriebssystem gewährleistet, das auf Einprozessormaschinen die Prozesse alle paar Millisekunden umschaltet. Daher ist das Programm nicht wirklich parallel, sondern das Betriebssystem gaukelt uns dies durch verzahnte Bearbeitung der Prozesse vor. Wenn mehrere Prozessoren oder mehrere Prozessor-Kerne am Werke sind, werden die Programmteile tatsächlich parallel abgearbeitet. Aber ob nur ein kleines Männchen oder beliebig viele im Rechner arbeiten, soll uns egal sein. Der Teil des Betriebssystems, der die Umschaltung übernimmt, heißt Scheduler. Die dem Betriebssystem bekannten aktiven Programme bestehen aus Prozessen. Ein Prozess setzt sich aus dem Programmcode und den Daten zusammen und besitzt einen eigenen Adressraum. Des Weiteren gehören Ressourcen wie geöffnete Dateien oder belegte Schnittstellen dazu. Die virtuelle Speicherverwaltung des Betriebssystems trennt die Adressräume der einzelnen Prozesse. Dadurch ist es nicht möglich, dass ein Prozess den Speicherraum eines anderen Prozesses korrumpiert; er sieht den anderen Speicherbereich nicht. Damit Prozesse untereinander Daten austauschen können, wird ein besonderer Speicherbereich als Shared Memory markiert. Amoklaufende Programme sind zwar möglich, werden jedoch vom Betriebssystem gestoppt. ### 11.1.1 Threads und Prozesse Bei modernen Betriebssystemen gehört zu jedem Prozess mindestens ein Thread (zu Deutsch »Faden« oder »Ausführungsstrang«), der den Programmcode ausführt. Damit werden also genau genommen die Prozesse nicht mehr parallel ausgeführt, sondern nur die Threads. Innerhalb eines Prozesses kann es mehrere Threads geben, die alle zusammen in demselben Adressraum ablaufen. Die einzelnen Threads eines Prozesses können untereinander auf ihre öffentlichen Daten zugreifen. Hier klicken, um das Bild zu vergrößern Abbildung 11.1 Windows zeigt im Task-Manager die Anzahl laufender Threads an. Die Programmierung von Threads ist in Java einfach möglich, und die quasi parallel ablaufenden Aktivitäten ergeben für den Benutzer den Eindruck von Gleichzeitigkeit. In Java ist auch multithreaded Software möglich, wenn das Betriebssystem des Rechners keine Threads direkt verwendet. In diesem Fall simuliert die virtuelle Maschine die Parallelität, indem sie die Synchronisation und die verzahnte Ausführung regelt. Unterstützt das Betriebssystem Threads direkt, bildet die JVM die Thread-Verwaltung in der Regel auf das Betriebssystem ab. Dann haben wir es mit nativen Threads zu tun. Die 1:1-Abbildung ermöglicht eine einfache Verteilung auf Mehrprozessorsystemen, doch mit dem Nachteil, dass das Betriebssystem in den Threads auch Bibliotheksaufrufe ausführen kann, zum Beispiel, um das Ein- und Ausgabesystem zu verwenden oder für grafische Ausgaben. Damit dies ohne Probleme funktioniert, müssen die Bibliotheken jedoch thread-sicher sein. Damit hatten die Unix-Versionen in der Vergangenheit Probleme: insbesondere die grafischen Standardbibliotheken X11 und Motif waren lange nicht thread-sicher. Um schwerwiegenden Problemen mit grafischen Oberflächen aus dem Weg zu gehen, haben die Entwickler daher auf eine native Multithreading-Umgebung zunächst verzichtet. Ob die Laufzeitumgebung native Threads nutzt oder nicht, steht nicht in der Spezifikation der JVM. Auch die Sprachdefinition lässt bewusst die Art der Implementierung frei. Was die Sprache jedoch garantieren kann, ist die korrekt verzahnte Ausführung. Hier können Probleme auftreten, die Datenbankfreunde von Transaktionen her kennen. Es besteht die Gefahr konkurrierender Zugriffe auf gemeinsam genutzte Ressourcen. Um dies zu vermeiden, kann der Programmierer durch synchronisierte Programmblöcke gegenseitigen Ausschluss sicherstellen. Damit steigt aber auch die Gefahr für Verklemmungen (engl. deadlocks), die der Entwickler selbst vermeiden muss. ### 11.1.2 Wie parallele Programme die Geschwindigkeit steigern können Auf den ersten Blick ist nicht ersichtlich, warum auf einem Einprozessorsystem die nebenläufige Abarbeitung eines Programms geschwindigkeitssteigernd sein kann. Betrachten wir daher ein Programm, das eine Folge von Anweisungen ausführt. Die Programmsequenz dient zum Visualisieren eines Datenbank-Reports. Zunächst wird ein Fenster zur Fortschrittsanzeige dargestellt. Anschließend werden die Daten analysiert und der Fortschrittsbalken kontinuierlich aktualisiert. Schließlich werden die Ergebnisse in eine Datei geschrieben. Die Schritte sind:
1. | Baue ein Fenster auf.
2. | Öffne die Datenbank vom Netz-Server, und lies die Datensätze.
3. | Analysiere die Daten, und visualisiere den Fortschritt.
4. | Öffne die Datei, und schreibe den erstellten Report.
Was auf den ersten Blick wie ein typisches sequenzielles Programm aussieht, kann durch geschickte Parallelisierung beschleunigt werden. Zum Verständnis ziehen wir noch einmal den Vergleich mit Prozessen. Nehmen wir an, auf einer Einprozessormaschine sind fünf Benutzer angemeldet, die im Editor Quelltext tippen und hin und wieder den Java-Compiler bemühen. Die Benutzer bekämen vermutlich die Belastung des Systems durch die anderen nicht mit, denn Editor-Operationen lasten den Prozessor nicht aus. Wenn Dateien compiliert und somit vom Hintergrundspeicher in den Hauptspeicher transferiert werden, ist der Prozessor schon besser ausgelastet, doch geschieht dies nicht regelmäßig. Im Idealfall übersetzen alle Benutzer nur dann, wenn die anderen gerade nicht übersetzen – im schlechtesten Fall möchten natürlich alle Benutzer gleichzeitig übersetzen. Übertragen wir die Verteilung auf unser Problem, nämlich wie der Datenbank-Report schneller zusammengestellt werden kann. Beginnen wir mit der Überlegung, welche Operationen parallel ausgeführt werden können: Das Öffnen von Fenster, Ausgabedatei und Datenbank kann parallel geschehen. Das Lesen neuer Datensätze und das Analysieren alter Daten kann gleichzeitig erfolgen. Alte analysierte Werte können während der neuen Analyse in die Datei geschrieben werden. Wenn die Operationen wirklich parallel ausgeführt werden, lässt sich bei Mehrprozessorsystemen ein enormer Leistungszuwachs verzeichnen. Doch interessanterweise ergibt sich dieser auch bei nur einem Prozessor, was in den Aufgaben begründet liegt. Denn bei den gleichzeitig auszuführenden Aufgaben handelt es sich um unterschiedliche Ressourcen. Wenn die grafische Oberfläche das Fenster aufbaut, braucht sie dazu natürlich Rechenzeit. Parallel kann die Datei geöffnet werden, wobei weniger Prozessorleistung gefragt ist, da die vergleichsweise träge Festplatte angesprochen wird. Das Öffnen der Datenbank wird auf den Datenbank-Server im Netzwerk abgewälzt. Die Geschwindigkeit hängt von der Belastung des Servers und des Netzes ab. Wenn anschließend die Daten gelesen werden, muss die Verbindung zum Datenbank-Server natürlich stehen. Daher sollten wir zuerst die Verbindung aufbauen. Ist die Verbindung hergestellt, lassen sich über das Netzwerk Daten in einen Puffer holen. Der Prozessor wird nicht belastet, vielmehr der Server auf der Gegenseite und das Netzwerk. Während der Prozessor also vor sich hin döst und sich langweilt, können wir ihn besser beschäftigen, indem er alte Daten analysiert. Wir verwenden hierfür zwei Puffer: In den einen lädt ein Thread die Daten, während ein zweiter Thread die Daten im anderen Puffer analysiert. Dann werden die Rollen der beiden Puffer getauscht. Jetzt ist der Prozessor beschäftigt. Er ist aber vermutlich fertig, bevor die neuen Daten über das Netzwerk eingetroffen sind. In der Zwischenzeit können die Report-Daten in den Report geschrieben werden; eine Aufgabe, die wieder die Festplatte belastet und weniger den Prozessor. Wir sehen an diesem Beispiel, dass durch hohe Parallelisierung eine Leistungssteigerung möglich ist, da die bei langsamen Operationen anfallenden Wartezeiten genutzt werden können. Langsame Arbeitsschritte lasten den Prozessor nicht aus, und die anfallende Wartezeit vom Prozessor beim Netzwerkzugriff auf eine Datenbank kann für andere Aktivitäten genutzt werden. Die Tabelle gibt die Elemente zum Kombinieren noch einmal an:

Tabelle 11.1 Parallelisierbare Ressourcen

Ressource | Belastung
Hauptspeicherzugriffe | Prozessor
Dateioperationen | Festplatte
Datenbankzugriff | Server, Netzwerkverbindung
Das Beispiel macht auch deutlich, dass die Nebenläufigkeit gut geplant werden muss. Nur wenn verzahnte Aktivitäten unterschiedliche Ressourcen verwenden, resultiert daraus auf Einprozessorsystemen ein Geschwindigkeitsvorteil. Daher ist ein paralleler Sortieralgorithmus nicht sinnvoll. Das zweite Problem ist die zusätzliche Synchronisation, die das Programmieren erschwert. Wir müssen auf das Ergebnis einer Operation warten, damit wir mit der Bearbeitung fortfahren können. Diesem Problem widmen wir uns in einem eigenen Abschnitt. Doch nun zur Programmierung von Threads in Java. ### 11.1.3 Was Java für Nebenläufigkeit alles bietet Für nebenläufige Programme sieht die Java-Bibliothek eine Reihe von Klassen, Schnittstellen und Aufzählungen vor:
1. | Thread. Jeder laufende Thread ist ein Exemplar dieser Klasse.
2. | Runnable. Beschreibt den Programmcode, den die JVM parallel ausführen soll.
3. | Lock. Dient zum Markieren von kritischen Abschnitten, in denen sich nur ein Thread befinden darf.
4. | Condition. Threads können auf die Benachrichtigung anderer Threads warten.
5. | ThreadGroup. Threads sind hierarchisch in Gruppen einsortiert.
### Ihr Kommentar Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
<< zurück | <top> | vor >>
Zum Katalog
Java ist auch eine Insel Jetzt bestellen
Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen? Ihre Meinung
Tipp
| Coding for Fun
Buchempfehlungen
| Objektorientierte Programmierung
| Einstieg in Eclipse 3.4
| Java 6 lernen mit Eclipse
| NetBeans Platform 6
| Java und XML
| Visual C# 2008
| IT-Handbuch für Fachinformatiker
| C++ von A bis Z
Shopping
Versandkostenfrei bestellen in Deutschland und Österreich Info

Copyright © Galileo Press 2009
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, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de