Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 16 Einführung in die nebenläufige Programmierung
Pfeil 16.1 Nebenläufigkeit und Parallelität
Pfeil 16.1.1 Multitasking, Prozesse und Threads
Pfeil 16.1.2 Threads und Prozesse
Pfeil 16.1.3 Wie nebenläufige Programme die Geschwindigkeit steigern können
Pfeil 16.1.4 Was Java für Nebenläufigkeit alles bietet
Pfeil 16.2 Existierende Threads und neue Threads erzeugen
Pfeil 16.2.1 Main-Thread
Pfeil 16.2.2 Wer bin ich?
Pfeil 16.2.3 Die Schnittstelle Runnable implementieren
Pfeil 16.2.4 Thread mit Runnable starten
Pfeil 16.2.5 Runnable parametrisieren
Pfeil 16.2.6 Die Klasse Thread erweitern
Pfeil 16.3 Thread-Eigenschaften und Zustände
Pfeil 16.3.1 Der Name eines Threads
Pfeil 16.3.2 Die Zustände eines Threads *
Pfeil 16.3.3 Schläfer gesucht
Pfeil 16.3.4 Wann Threads fertig sind
Pfeil 16.3.5 Einen Thread höflich mit Interrupt beenden
Pfeil 16.3.6 Unbehandelte Ausnahmen, Thread-Ende und UncaughtExceptionHandler
Pfeil 16.3.7 Der stop() von außen und die Rettung mit ThreadDeath *
Pfeil 16.3.8 Ein Rendezvous mit join(…) *
Pfeil 16.3.9 Arbeit niederlegen und wieder aufnehmen *
Pfeil 16.3.10 Priorität *
Pfeil 16.4 Der Ausführer (Executor) kommt
Pfeil 16.4.1 Die Schnittstelle Executor
Pfeil 16.4.2 Glücklich in der Gruppe – die Thread-Pools
Pfeil 16.4.3 Threads mit Rückgabe über Callable
Pfeil 16.4.4 Erinnerungen an die Zukunft – die Future-Rückgabe
Pfeil 16.4.5 Mehrere Callable-Objekte abarbeiten
Pfeil 16.4.6 CompletionService und ExecutorCompletionService
Pfeil 16.4.7 ScheduledExecutorService: wiederholende Aufgaben und Zeitsteuerungen
Pfeil 16.4.8 Asynchrones Programmieren mit CompletableFuture (CompletionStage)
Pfeil 16.5 Zum Weiterlesen
 

Zum Seitenanfang

16    Einführung in die nebenläufige Programmierung Zur vorigen ÜberschriftZur nächsten Überschrift

»just be.«

– Calvin Klein (* 1942)

 

Zum Seitenanfang

16.1    Nebenläufigkeit und Parallelität Zur vorigen ÜberschriftZur nächsten Überschrift

Computersysteme lösen Probleme in der echten Welt, sodass wir zum Einstieg auch in der Realwelt bleiben, um uns dem Umfeld der nebenläufigen Programmierung zu nähern. Gehen wir durch die Welt, bemerken wir viele Dinge, die gleichzeitig passieren: Die Sonne scheint, auf der Straße fahren Mofas, Autos werden gelenkt, das Radio spielt, Menschen sprechen, einige essen, Hunde tollen auf der Wiese. Diese Dinge passieren nicht nur gleichzeitig, sondern es gibt mannigfaltige Abhängigkeiten, wie Wartesituationen: An der roten Ampel warten einige Autos, während bei der grünen Ampel Menschen über die Straße gehen – beim Signalwechsel dreht sich das Spiel um.

Wenn viele Dinge gleichzeitig passieren, nennen wir ein interagierendes System nebenläufig. Dabei gibt es Vorgänge, die echt parallel ausgeführt werden können, und bei manchen Dingen sieht es so aus, als ob sie parallel passierten, aber in Wirklichkeit passieren sie nur schnell hintereinander. Was wir dann wahrnehmen, ist eine Quasiparallelität. Wenn zwei Menschen etwa gleichzeitig essen, ist das parallel, aber wenn jemand isst und atmet, so sieht das zwar von außen gleichzeitig aus, ist es aber nicht, sondern Schlucken und Atmen sind sequenziell.[ 248 ](Lassen wir Kleinkinder einmal außen vor. ) Auf Software übertragen: Die gleichzeitige Abarbeitung von Programmen und Nutzung von Ressourcen ist nebenläufig; es liegt an der technischen Realisierung der Maschine (also Hardware), ob diese Nebenläufigkeit durch parallele Abarbeitung – etwa durch mehrere Prozessoren oder Kerne – auch wirklich umgesetzt wird.

Nebenläufige Programme werden in Java durch Threads realisiert, wobei jeder Thread einer Aufgabe entspricht. Im Idealfall findet die Abarbeitung der Threads auch parallel statt, wenn die Maschine mehrere Prozessoren oder Kerne hat. Ein Programm, das nebenläufig realisiert ist, kann durch zwei Prozessoren bzw. Kerne durch parallele Abarbeitung in der Zeit halbiert werden; es muss aber nicht halbiert werden: Es ist immer noch Sache des Betriebssystems, wie es die Threads ausführt.

 

Zum Seitenanfang

16.1.1    Multitasking, Prozesse und Threads Zur vorigen ÜberschriftZur nächsten Überschrift

Ein modernes Betriebssystem gibt dem Benutzer die Illusion, dass verschiedene Programme gleichzeitig ausgeführt werden – die Betriebssysteme unterstützen das Multitasking und nennen sich multitaskingfähig. Bei der Ausführung eines Programms erzeugt das Betriebssystem einen sogenannten Prozess – alle laufenden 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. Amok laufende Programme sind zwar möglich, werden jedoch vom Betriebssystem gestoppt.

Diese Nebenläufigkeit der Prozesse wird durch das Betriebssystem gewährleistet, das auf Einprozessormaschinen die Prozesse alle paar Millisekunden umschaltet. Der Teil des Betriebssystems, der die Umschaltung übernimmt, heißt Dispatcher. Daher ist das Programm bei einem Prozessor (mit nur einem Kern) zwar nebenläufig, aber nicht wirklich parallel, sondern das Betriebssystem gaukelt uns die Nebenläufigkeit durch eine verzahnte Bearbeitung der Prozesse vor. Wenn mehrere Prozessoren oder mehrere Prozessorkerne am Werke sind, werden die Programmteile tatsächlich parallel abgearbeitet. Die Informationen, welche Prozesse welche Rechenzeit bekommen, stammen vom Scheduler.

 

Zum Seitenanfang

16.1.2    Threads und Prozesse Zur vorigen ÜberschriftZur nächsten Überschrift

Bei modernen Betriebssystemen gehört zu jedem Prozess mindestens ein Thread (zu Deutsch Faden oder Ausführungsstrang), der den Programmcode ausführt. Damit werden genau genommen die Prozesse nicht mehr nebenläufig ausgeführt, sondern nur die Threads. Innerhalb eines Prozesses kann es mehrere Threads geben, die alle zusammen in demselben Adressraum ablaufen (siehe Abbildung 16.1). Die einzelnen Threads eines Prozesses können untereinander auf ihre öffentlichen Daten zugreifen.

Java-Threads sind native Betriebssystem-Threads

Die Programmierung von Threads ist in Java einfach möglich, und die nebenläufigen Aktivitäten vermitteln dem Benutzer den Eindruck von Gleichzeitigkeit. Alle modernen Betriebssysteme unterstützen Threads direkt, und so 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 Mehrkern- bzw. Mehrprozessorsystemen, da sich das Betriebssystem um die ganze Thread-Verwaltung kümmert.

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 einen gegenseitigen Ausschluss sicherstellen. Damit steigt aber auch die Gefahr von Verklemmungen (engl. deadlocks), die der Entwickler selbst vermeiden muss.

Windows zeigt im Task-Manager die Anzahl laufender Threads an.

Abbildung 16.1    Windows zeigt im Task-Manager die Anzahl laufender Threads an.

 

Zum Seitenanfang

16.1.3    Wie nebenläufige Programme die Geschwindigkeit steigern können Zur vorigen ÜberschriftZur nächsten Überschrift

Auf den ersten Blick ist es 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 wird kontinuierlich aktualisiert. Schließlich werden die Ergebnisse in eine Datei geschrieben. Die Schritte sind:

  1. Baue ein Fenster auf.

  2. Öffne die Datenbank vom Netzserver, 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 Nebenläufigkeit und parallele Abarbeitung beschleunigt werden.

Damit dies besser zu verstehen ist, 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 nebenläufig ausgeführt werden können:

  • Das Öffnen des Fensters und das Öffnen der Datenbank können 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 Datenbankserver 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 Datenbankserver 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, sondern der Server auf der Gegenseite und das Netzwerk. Anstatt dass der Prozessor vor sich hin döst und sich langweilt, können wir ihn besser beschäftigen, indem wir ihn alte Daten analysieren lassen. 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 nebenläufige Modellierung 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 Wartezeit, die für den Prozessor beim Netzwerkzugriff auf eine Datenbank anfällt, kann für andere Aktivitäten genutzt werden. Tabelle 16.1 fasst die Elemente zum Kombinieren noch einmal zusammen.

Ressource

Belastung

Hauptspeicherzugriffe

Prozessor

Dateioperationen

Festplatte

Datenbankzugriff

Server, Netzwerkverbindung

Tabelle 16.1    Parallelisierbare Ressourcen

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 nebenläufig implementierter Sortieralgorithmus mit einem Prozessor(kern) 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.

 

Zum Seitenanfang

16.1.4    Was Java für Nebenläufigkeit alles bietet Zur vorigen ÜberschriftZur nächsten Überschrift

Für nebenläufige Programme sieht die Java-Bibliothek eine Reihe von Klassen, Schnittstellen und Aufzählungen vor:

  • Thread: Jeder laufende Thread ist ein Exemplar dieser Klasse.

  • Runnable: Beschreibt den Programmcode, den die JVM nebenläufig ausführen soll.

  • Lock: Dient zum Markieren von kritischen Abschnitten, in denen sich nur ein Thread befinden darf.

  • Condition: Threads können auf die Benachrichtigung anderer Threads warten.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

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.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern