Sie sind hier
E-Book

Nebenläufige Programmierung mit Java

Konzepte und Programmiermodelle für Multicore-Systeme

AutorJörg Hettel, Manh Tien Tran
Verlagdpunkt
Erscheinungsjahr2016
Seitenanzahl378 Seiten
ISBN9783960880127
FormatPDF/ePUB
KopierschutzWasserzeichen
GerätePC/MAC/eReader/Tablet
Preis34,90 EUR
Damit die Performance-Möglichkeiten moderner Multicore-Rechner effizient genutzt werden, muss die Software dafür entsprechend entworfen und entwickelt werden. Für diese Aufgabe bietet insbesondere Java vielfältige Konzepte an. Das Buch bietet eine fundierte Einführung in die nebenläufige Programmierung mit Java. Der Inhalt gliedert sich dabei in fünf Teile: Im ersten Teil wird das grundlegende Thread-Konzept besprochen und die Koordinierung nebenläufiger Programmflüsse durch rudimentäre Synchronisationsmechanismen erläutert. Im zweiten Teil werden weiterführende Konzepte wie Threadpools, Futures, Atomic-Variablen und Locks vorgestellt. Ergänzende Synchronisationsmechanismen zur Koordinierung mehrerer Threads werden im dritten Teil eingeführt. Teil vier bespricht das ForkJoin-Framework, die Parallel Streams und die Klasse CompletableFuture, mit denen auf einfache Art und Weise nebenläufige Programme erstellt werden können. Im fünften Teil findet der Leser Beispiele für die Anwendung der vorgestellten Konzepte und Klassen. Dabei werden auch das Thread-Konzept von JavaFX und Android sowie das Programmiermodell mit Aktoren vorgestellt. Der Anhang enthält einen Ausblick auf Java 9, das bezüglich des Concurrency-API kleine Neuerungen bringt. Alle Codebeispiele stehen auf der Webseite zum Buch zum Download bereit.

Jörg Hettel studierte Theoretische Physik und promovierte am Institut für Informationsverarbeitung und Kybernetik an der Universität Tübingen. Nach seiner Promotion war er als Berater bei nationalen und internationalen Unternehmen tätig. Er begleitete zahlreiche Firmen bei der Einführung von objektorientierten Technologien und übernahm als Softwarearchitekt Projektverantwortung. Seit 2003 ist er Professor an der Hochschule Kaiserslautern am Standort Zweibrücken. Seine aktuellen Arbeitsgebiete sind u.a. verteilte internetbasierte Transaktionssysteme und die Multicore-Programmierung. Manh Tien Tran studierte Informatik an der TU Braunschweig. Von 1987 bis 1995 war er wissenschaftlicher Mitarbeiter am Institut für Mathematik der Universität Hildesheim, wo er 1995 promovierte. Von 1995 bis 1998 war er als Softwareentwickler bei BOSCH Blaupunkt beschäftigt. 1999 wechselte er zu Harman Becker und war dort bis 2000 für Softwarearchitekturen zuständig. Seit 2000 ist er Professor an der Hochschule Kaiserslautern am Standort Zweibrücken. Seine aktuellen Arbeitsgebiete sind Frameworks, Embedded-Systeme und die Multicore-Programmierung.

Kaufen Sie hier:

Horizontale Tabs

Leseprobe

1 Einführung


Die meisten Computer können heute verschiedene Anweisungen parallel abarbeiten. Um diese zur Verfügung stehende Ressource auszunutzen, müssen wir sie bei der Softwareentwicklung entsprechend berücksichtigen. Die nebenläufige Programmierung wird deshalb häufiger eingesetzt. Der Umgang und die Koordinierung von Threads gehören heute zum Grundhandwerk eines guten Entwicklers.

1.1 Dimensionen der Parallelität


Bei Softwaresystemen gibt es verschiedene Ebenen, auf denen Parallelisierung eingesetzt werden kann bzw. bereits eingesetzt wird. Grundlegend kann zwischen Parallelität auf der Prozessorebene und der Systemebene unterschieden werden [26, 15]. Auf der Prozessorebene lassen sich die drei Bereiche Pipelining (Fließbandverarbeitung), superskalare Ausführung und Vektorisierung für die Parallelisierung identifizieren.

Auf der Systemebene können je nach Prozessoranordnung und Zugriffsart auf gemeinsam benutzte Daten folgende Varianten unterschieden werden:

  • Bei Multinode-Systemen wird die Aufgabe über verschiedene Rechner hinweg verteilt. Jeder einzelne Knoten (in der Regel ein eigenständiger Rechner) hat seinen eigenen Speicher und Prozessor. Man spricht in diesem Zusammenhang von verteilten Anwendungen.

  • Bei Multiprocessor-Systemen ist die Anwendung auf verschiedene Prozessoren verteilt, die sich in der Regel alle auf demselben Rechner (Mainboard) befinden und die alle auf denselben Hauptspeicher zugreifen, wobei die Zugriffszeiten nicht einheitlich sind. Jeder Prozessor hat darüber hinaus auch noch verschiedene Cache-Levels. Solche Systeme besitzen häufig eine sogenannte NUMA-Architektur (Non-Uniform Memory Access).

  • Bei Multicore-Systemen befinden sich verschiedene Rechenkerne in einem Prozessor, die sich den Hauptspeicher und zum Teil auch Caches teilen. Der Zugriff auf den Hauptspeicher ist von allen Kernen gleich schnell. Man spricht in diesem Zusammenhang von einer UMA-Architektur (Uniform Memory Access).

Neben den hier aufgeführten allgemeinen Unterscheidungsmerkmalen gibt es noch weitere, herstellerspezifische Erweiterungsebenen. Genannt sei hier z. B. das von Intel eingeführte Hyper-Threading. Dabei werden Lücken in der Fließbandverarbeitung mit Befehlen von anderen Prozessen möglichst aufgefüllt.

Hinweis

In dem vorliegenden Buch werden wir uns ausschließlich mit den Konzepten und Programmiermodellen für Multicore- bzw. Multiprocessor-Systeme mit Zugriff auf einen gemeinsam benutzten Hauptspeicher befassen, wobei wir auf die Besonderheiten der NUMA-Architektur nicht eingehen. Bei Java hat man außer der Verwendung der beiden VM-Flags -XX:+UseNUMA und -XX:+UseParallelGC kaum Einfluss auf das Speichermanagement.

1.2 Parallelität und Nebenläufigkeit


Zwei oder mehrere Aktivitäten (Tasks) heißen nebenläufig, wenn sie zeitgleich bearbeitet werden können. Dabei ist es unwichtig, ob zuerst der eine und dann der andere ausgeführt wird, ob sie in umgekehrter Reihenfolge oder gleichzeitig erledigt werden. Sie haben keine kausale Abhängigkeit, d.h., das Ergebnis einer Aktivität hat keine Wirkung auf das Ergebnis einer anderen und umgekehrt. Das Abstraktionskonzept für Nebenläufigkeit ist bei Java der Thread, der einem eigenständigen Kontrollfluss entspricht.

Besitzt ein Rechner mehr als eine CPU bzw. mehrere Rechenkerne, kann die Nebenläufigkeit parallel auf Hardwareebene realisiert werden. Dadurch besteht die Möglichkeit, die Abarbeitung eines Programms zu beschleunigen, wenn der zugehörige Kontrollfluss nebenläufige Tasks (Aktivitäten) beinhaltet. Dabei können moderne Hardware und Übersetzer nur bis zu einem gewissen Grad automatisch ermitteln, ob Anweisungen sequenziell oder parallel (gleichzeitig) ausgeführt werden können. Damit Programme die Möglichkeiten der Multicore-Prozessoren voll ausnutzen können, müssen wir die Parallelität explizit im Code berücksichtigen.

Die nebenläufige bzw. parallele Programmierung beschäftigt sich zum einen mit Techniken, wie ein Programm in einzelne, nebenläufige Abschnitte/Teilaktivitäten zerlegt werden kann, zum anderen mit den verschiedenen Mechanismen, mit denen nebenläufige Abläufe synchronisiert und gesteuert werden können. So schlagen z. B. Mattson et al. in [37] ein »patternbasiertes« Vorgehen für das Design paralleler Anwendungen vor. Ähnliche Wege werden auch in [7] oder [38] aufgezeigt. Spezielle Design-Patterns für die nebenläufige Programmierung findet man in [15, 38, 42, 45].

1.2.1 Die Vorteile von Nebenläufigkeit

Der Einsatz von Nebenläufigkeit ermöglicht die Anwendung verschiedener neuer Programmierkonzepte. Der offensichtlichste Vorteil ist die Steigerung der Performance. Auf Maschinen mit mehreren CPUs kann zum Beispiel das Sortieren eines großen Arrays auf mehrere Threads verteilt werden. Dadurch kann die zur Verfügung stehende Rechenleistung voll ausgenutzt und somit die Leistungsfähigkeit der Anwendung verbessert werden. Ein weiterer Aspekt ist, dass Threads ihre Aktivitäten unterbrechen und wiederaufnehmen können. Durch Auslagerung der blockierenden Tätigkeiten in separate Threads kann die CPU in der Zwischenzeit andere Aufgaben erledigen. Hierdurch ist es möglich, asynchrone Schnittstellen zu implementieren und somit die Anwendung reaktiv zu halten. Dieser Gesichtspunkt gewinnt immer mehr an Bedeutung.

1.2.2 Die Nachteile von Nebenläufigkeit

Der Einsatz von Nebenläufigkeit hat aber nicht nur Vorteile. Er kann unter Umständen sogar mehr Probleme verursachen, als damit gelöst werden. Programmcode mit Multithreading-Konzepten ist nämlich oft schwer zu verstehen und mit hohem Aufwand zu warten. Insbesondere wird das Debugging erschwert, da die CPU-Zuteilung an die Threads nicht deterministisch ist und ein Programm somit jedes Mal verschieden verzahnt abläuft.

Parallel ablaufende Threads müssen koordiniert werden, sodass man immer mehrere Programmflüsse im Auge haben muss, insbesondere wenn sie auf gemeinsame Daten zugreifen. Wenn eine Variable von einem Thread geschrieben wird, während der andere sie liest, kann das dazu führen, dass das System in einen falschen Zustand gerät. Für gemeinsam verwendete Objekte müssen gesondert Synchronisationsmechanismen eingesetzt werden, um konsistente Zustände sicherzustellen. Des Weiteren kommen auch Cache-Effekte hinzu. Laufen zwei Threads auf verschiedenen Kernen, so besitzt jeder seine eigene Sicht auf die Variablenwerte. Man muss nun dafür Sorge tragen, dass gemeinsam benutzte Daten, die aus Performance-Gründen in den Caches gehalten werden, immer synchron bleiben. Weiter ist es möglich, dass sich Threads gegenseitig in ihrem Fortkommen behindern oder sogar verklemmen.

1.2.3 Sicherer Umgang mit Nebenläufigkeit

Den verschiedenen Nachteilen versucht man durch die Einführung von Parallelisierungs- und Synchronisationskonzepten auf höherer Ebene entgegenzuwirken. Ziel ist es, dass Entwickler möglichst wenig mit Low-Level-Synchronisation und Thread-Koordination in Berührung kommen. Hierzu gibt es verschiedene Vorgehensweisen. So wird z. B. bei C/C++ mit OpenMP1 die Steuerung der Parallelität deklarativ über #pragma im Code verankert. Der Compiler erzeugt aufgrund dieser Angaben parallel ablaufenden Code. Die Sprache Cilk erweitert C/C++ um neue Schlüsselworte, wie z. B. cilk_for2.

Java geht hier den Weg über die Bereitstellung einer »Concurrency-Bibliothek«, die mit Java 5 eingeführt wurde und sukzessive erweitert wird. Nachdem zuerst Abstraktions- und Synchronisationskonzepte wie Thread-pools, Locks, Semaphore und Barrieren angeboten wurden, sind mit Java 7 und Java 8 auch Parallelisierungsframeworks hinzugekommen. Nicht vergessen werden darf hier auch die Einführung Thread-sicherer Datenstrukturen, die unverzichtbar bei der Implementierung von Multithreaded-Anwendungen sind. Der Umgang mit diesen High-Level-Abstraktionen ist bequem und einfach. Nichtsdestotrotz gibt es auch hier Fallen, die man nur dann erkennt, wenn man die zugrunde liegenden Low-Level-Konzepte beherrscht. Deshalb werden im ersten Teil des Buches die Basiskonzepte ausführlich erklärt, auch wenn diese im direkten Praxiseinsatz immer mehr an Bedeutung verlieren.

1.3 Maße für die Parallelisierung


Neben der Schwierigkeit, korrekte nebenläufige Programme zu entwickeln, gibt es auch inhärente Grenzen für die Beschleunigung durch Parallelisierung. Eine wichtige Maßzahl für den Performance-Gewinn ist der Speedup (Beschleunigung bzw. Leistungssteigerung), der wie folgt definiert ist:

Hierbei ist Tseq die Laufzeit mit einem Kern und Tpar die Laufzeit mit mehreren.

1.3.1 Die Gesetze von Amdahl und Gustafson

Eine erste Näherung für den Speedup liefert das Gesetz von Amdahl [2]. Hier...

Blick ins Buch
Inhaltsverzeichnis
Vorwort5
Inhaltsverzeichnis9
Einführung15
Dimensionen der Parallelität15
Parallelität und Nebenläufigkeit16
Die Vorteile von Nebenläufigkeit17
Die Nachteile von Nebenläufigkeit17
Sicherer Umgang mit Nebenläufigkeit18
Maße für die Parallelisierung18
Die Gesetze von Amdahl und Gustafson18
Work-Span-Analyse20
Parallelitätsmodelle21
I Grundlegende Konzepte23
Das Thread-Konzept von Java25
Der main-Thread25
Erzeugung und Starten von Threads26
Thread-Erzeugung durch Vererbung27
Thread-Erzeugung mit Runnable-Objekten30
Der Lebenszyklus von Threads33
Beendigung eines Threads34
Auf das Ende eines Threads warten35
Aktives Beenden von Threads35
Unterbrechung mit interrupt38
Thread-Zustände40
Weitere Eigenschaften eines Thread-Objekts41
Thread-Priorität41
Daemon-Eigenschaft42
Exception-Handler43
Zusammenfassung45
Konkurrierende Zugriffe auf Daten47
Ein einleitendes Beispiel47
Java-Speichermodell48
Stacks und Heap49
Speicher auf der Hardwareebene51
Probleme mit gemeinsam nutzbaren Daten52
Sequenzielle Konsistenz53
Thread-sichere Daten und unveränderliche Objekte55
Unveränderbare Objekte56
Volatile-Attribute57
Final-Attributte59
Thread-lokale Daten60
Fallstricke63
Zusammenfassung64
Elementare Synchronisationsmechanismen65
Schlüsselwort synchronized65
Synchronized-Methoden65
Synchronized-Blöcke67
Beispiel: Thread-sicheres Singleton68
Monitorkonzept bei Java70
Fallstricke71
Zusammenfassung76
Grundlegende Thread-Steuerung77
Bedingungsvariablen und Signalisieren77
Regeln zum Umgang mit wait, notify und notifyAll83
Zusammenfassung86
II Weiterführende Konzepte87
Threadpools89
Das Poolkonzept und die Klasse Executors89
Executors mit eigener ThreadFactory92
Explizite ThreadPoolExecutor-Erzeugung92
Benutzerdefinierter ThreadPoolExecutor93
Future- und Callable-Schnittstelle94
Callable, Future und FutureTask95
Callable, Future und ExecutorService95
Callable und ThreadPoolExecutor98
Callable und ScheduledThreadPoolExecutor102
Callable und ForkJoinPool102
Exception-Handling104
Tipps für das Arbeiten mit Threadpools106
Zusammenfassung107
Atomic-Variablen109
Compare-and-Set-Operation110
Umgang mit Atomic-Variablen111
Atomic-Skalare111
Atomic-Referenzen114
Accumulator und Adder in Java 8116
Zusammenfassung118
Lock-Objekte und Semaphore119
Lock-Objekte120
Das Lock-Interface120
ReentrantLock123
Das Condition-Interface125
ReadWriteLock129
StampedLock131
Semaphore134
Zusammenfassung137
Thread-sichere Container139
Collection-Typen139
Thread-sichere Collections141
Synchronisierte Collections141
Unmodifiable Collections143
Concurrent Collections144
Zusammenfassung149
III Ergänzende Synchronisationsmechanismen151
Exchanger und BlockingQueue153
Exchanger153
Queues157
Das Erzeuger-Verbraucher-Muster160
Varianten163
Pipeline von Erzeugern und Verbrauchern163
Erzeuger-Verbraucher-Muster mit Empfangsbestätigung164
Erzeuger-Verbraucher-Muster mit Work-Stealing165
Zusammenfassung171
CountDownLatch und CyclicBarrier173
CountDownLatch173
CyclicBarrier176
Zusammenfassung181
Phaser183
Das Konzept des Phasers183
Phaser als CountDownLatch184
Phaser als CyclicBarrier187
Phaser als variable Barriere188
Zusammenspiel mit dem ForkJoin-Threadpool192
Zusammenfassung193
IV Parallelisierungsframeworks195
Das ForkJoin-Framework197
Grundprinzip des ForkJoin-Patterns197
Programmiermodell198
Einsatz von RecursiveAction200
Einsatz von RecursiveTask203
Einsatz von CountedCompleter205
Work-Stealing-Verfahren208
Zusammenfassung211
Parallele Array- und Stream-Verarbeitung213
Parallele Array-Verarbeitung213
Parallele Transformation213
Paralleles Sortieren214
Parallele Präfixbildung215
Funktionsprinzip der Stream-Verarbeitung217
Funktionale Interfaces218
Erzeugung von Streams219
Transformations- und Manipulationsoperationen222
Auswertungen von Streams225
Eigenschaften und Operationsoptimierung230
Parallele Stream-Verarbeitung: Datenparallelität231
Arbeitsweise und korrekte Benutzung232
Parallele Reduzierer234
Parallele Collectoren237
Funktionsweise von Spliteratoren242
Benutzerdefinierte Spliteratoren244
Zusammenfassung249
CompletableFuture251
CompletableFuture als Erweiterung des Future-Patterns251
Design von asynchronen APIs255
Asynchrone APIs mit Future256
Asynchrone APIs mit CompletableFuture256
Asynchrone Verarbeitung: Task-Parallelität258
Das Starten einer asynchronen Verarbeitung258
Definition einer asynchronen Verarbeitungskette259
Das Arbeiten mit CompletableFutures261
Das Konzept des CompletionStage262
Lineare Kompositionsmöglichkeiten263
Verzweigen und Vereinen266
Synchronisationsbarrieren270
Fehlerbehandlung und Abbruch einer Verarbeitung271
Zusammenfassung273
V Fallbeispiele275
Asynchrones Logging277
Lösung mit Thread-lokalen Daten278
Verbesserte Version (Exchanger)280
Datenstrukturen in Multithreaded-Umgebungen285
Liste als sortierte Menge285
Blockierende Lösungen (Locks)290
Grobgranulare Synchronisierung290
Feingranulare Synchronisierung290
Optimistische Synchronisierung293
Lockfreie Lösung (AtomicMarkableReference)294
The Dining Philosophers Problem301
Basisalgorithmus302
Lösungsvarianten (Semaphore und Lock)302
Lösung mit einem Semaphor303
Lösung mit asymmetrischer Lock-Anforderung304
Lösung mithilfe eines Koordinators305
Lösung mit asymmetrischer Wait-Release-Strategie307
Minimal aufspannende Bäume309
Graphen und Spannbäume309
Der Prim-Algorithmus311
Funktionsweise des Algorithmus311
Implementierung des Algorithmus313
Parallelisierung (Phaser)315
Mergesort319
Funktionsprinzip des Algorithmus319
Parallelisierung (ForkJoin-Framework)321
Der k-Mean-Clusteralgorithmus323
Der k-Mean-Algorithmus323
Parallelisierung (Parallel Streams)325
Datenmodell325
Hilfsmethoden325
Implementierung326
Variante mit benutzerdefiniertem Collector330
RSA-Schlüsselerzeugung335
Verfahren für die Schlüsselerzeugung335
Parallelisierung (CompletableFuture)337
Threads bei JavaFX341
Ein einfaches Beispiel341
JavaFX-Concurrent-API343
Handler-Konzept bei Android349
UI-Thread und nebenläufige Aktivitäten349
Messages, Message-Queue, Looper350
Handler352
Aktoren355
Aktorenmodell355
Beispielimplementierung mit Akka356
Nachrichten357
Beteiligte Aktoren359
Starten der Anwendung361
VI Anhang363
Ausblick auf Java 9365
Die Flow-Interfaces365
Literaturverzeichnis371
Index371
www.dpunkt.de0

Weitere E-Books zum Thema: Programmiersprachen - Softwareentwicklung

ASP.NET Shortcut

E-Book ASP.NET Shortcut
Format: PDF

Shortcut-Tipps für ASP.NET-Profis Die neue .NET-Version der Active Server Pages stellt eine Umgebung zur Entwicklung von Web-Applikationen im .NET-Framework bereit. Viele aus der Desktop-…

ASP.NET Shortcut

E-Book ASP.NET Shortcut
Format: PDF

Shortcut-Tipps für ASP.NET-Profis Die neue .NET-Version der Active Server Pages stellt eine Umgebung zur Entwicklung von Web-Applikationen im .NET-Framework bereit. Viele aus der Desktop-…

ASP.NET Shortcut

E-Book ASP.NET Shortcut
Format: PDF

Shortcut-Tipps für ASP.NET-Profis Die neue .NET-Version der Active Server Pages stellt eine Umgebung zur Entwicklung von Web-Applikationen im .NET-Framework bereit. Viele aus der Desktop-…

Programmieren lernen in PHP 5

E-Book Programmieren lernen in PHP 5
Format: PDF

Mit der Version 5 erreicht PHP einen bemerkenswerten Reifegrad, der PHP zu einer festen Größe in der Welt der Webprogrammierung macht. Gerade die leichte Erlernbarkeit macht PHP zur idealen…

Mathematik für Informatiker

E-Book Mathematik für Informatiker
Format: PDF

Die Informatik entwickelt sich in einer unglaublichen Geschwindigkeit. Häufig ist die Mathematik Grundlage von Neuerungen. Deshalb ist sie unverzichtbares Werkzeug jedes Informatikers und Pflichtfach…

Mathematik für Informatiker

E-Book Mathematik für Informatiker
Format: PDF

Die Informatik entwickelt sich in einer unglaublichen Geschwindigkeit. Häufig ist die Mathematik Grundlage von Neuerungen. Deshalb ist sie unverzichtbares Werkzeug jedes Informatikers und Pflichtfach…

Mathematik für Informatiker

E-Book Mathematik für Informatiker
Format: PDF

Die Informatik entwickelt sich in einer unglaublichen Geschwindigkeit. Häufig ist die Mathematik Grundlage von Neuerungen. Deshalb ist sie unverzichtbares Werkzeug jedes Informatikers und Pflichtfach…

Weitere Zeitschriften

FESTIVAL Christmas

FESTIVAL Christmas

Fachzeitschriften für Weihnachtsartikel, Geschenke, Floristik, Papeterie und vieles mehr! FESTIVAL Christmas: Die erste und einzige internationale Weihnachts-Fachzeitschrift seit 1994 auf dem ...

Menschen. Inklusiv leben

Menschen. Inklusiv leben

MENSCHEN. das magazin informiert über Themen, die das Zusammenleben von Menschen in der Gesellschaft bestimmen -und dies konsequent aus Perspektive der Betroffenen. Die Menschen, um die es geht, ...

BIELEFELD GEHT AUS

BIELEFELD GEHT AUS

Freizeit- und Gastronomieführer mit umfangreichem Serviceteil, mehr als 700 Tipps und Adressen für Tag- und Nachtschwärmer Bielefeld genießen Westfälisch und weltoffen – das zeichnet nicht ...

cards Karten cartes

cards Karten cartes

Die führende Zeitschrift für Zahlungsverkehr und Payments – international und branchenübergreifend, erscheint seit 1990 monatlich (viermal als Fachmagazin, achtmal als ...

küche + raum

küche + raum

Internationale Fachzeitschrift für Küchenforschung und Küchenplanung. Mit Fachinformationen für Küchenfachhändler, -spezialisten und -planer in Küchenstudios, Möbelfachgeschäften und den ...

IT-BUSINESS

IT-BUSINESS

IT-BUSINESS ist seit mehr als 25 Jahren die Fachzeitschrift für den IT-Markt Sie liefert 2-wöchentlich fundiert recherchierte Themen, praxisbezogene Fallstudien, aktuelle Hintergrundberichte aus ...

building & automation

building & automation

Das Fachmagazin building & automation bietet dem Elektrohandwerker und Elektroplaner eine umfassende Übersicht über alle Produktneuheiten aus der Gebäudeautomation, der Installationstechnik, dem ...

F- 40

F- 40

Die Flugzeuge der Bundeswehr, Die F-40 Reihe behandelt das eingesetzte Fluggerät der Bundeswehr seit dem Aufbau von Luftwaffe, Heer und Marine. Jede Ausgabe befasst sich mit der genaue Entwicklungs- ...