Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.
 
Inhaltsverzeichnis
Vorwort
1 Neues in Java 8 und Java 7
2 Fortgeschrittene String-Verarbeitung
3 Threads und nebenläufige Programmierung
4 Datenstrukturen und Algorithmen
5 Raum und Zeit
6 Dateien, Verzeichnisse und Dateizugriffe
7 Datenströme
8 Die eXtensible Markup Language (XML)
9 Dateiformate
10 Grafische Oberflächen mit Swing
11 Grafikprogrammierung
12 JavaFX
13 Netzwerkprogrammierung
14 Verteilte Programmierung mit RMI
15 RESTful und SOAP-Web-Services
16 Technologien für die Infrastruktur
17 Typen, Reflection und Annotationen
18 Dynamische Übersetzung und Skriptsprachen
19 Logging und Monitoring
20 Sicherheitskonzepte
21 Datenbankmanagement mit JDBC
22 Java Native Interface (JNI)
23 Dienstprogramme für die Java-Umgebung
Stichwortverzeichnis

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java SE 8 Standard-Bibliothek von Christian Ullenboom
Das Handbuch für Java-Entwickler
Buch: Java SE 8 Standard-Bibliothek

Java SE 8 Standard-Bibliothek
Pfeil 3 Threads und nebenläufige Programmierung
Pfeil 3.1 Threads erzeugen
Pfeil 3.1.1 Threads über die Schnittstelle Runnable implementieren
Pfeil 3.1.2 Thread mit Runnable starten
Pfeil 3.1.3 Die Klasse Thread erweitern
Pfeil 3.2 Thread-Eigenschaften und -Zustände
Pfeil 3.2.1 Der Name eines Threads
Pfeil 3.2.2 Wer bin ich?
Pfeil 3.2.3 Die Zustände eines Threads *
Pfeil 3.2.4 Schläfer gesucht
Pfeil 3.2.5 Mit yield() auf Rechenzeit verzichten
Pfeil 3.2.6 Der Thread als Dämon
Pfeil 3.2.7 Freiheit für den Thread – das Ende
Pfeil 3.2.8 Einen Thread höflich mit Interrupt beenden
Pfeil 3.2.9 UncaughtExceptionHandler für unbehandelte Ausnahmen
Pfeil 3.2.10 Der stop() von außen und die Rettung mit ThreadDeath *
Pfeil 3.2.11 Ein Rendezvous mit join(…) *
Pfeil 3.2.12 Arbeit niederlegen und wieder aufnehmen *
Pfeil 3.2.13 Priorität *
Pfeil 3.3 Der Ausführer (Executor) kommt
Pfeil 3.3.1 Die Schnittstelle Executor
Pfeil 3.3.2 Glücklich in der Gruppe – die Thread-Pools
Pfeil 3.3.3 Threads mit Rückgabe über Callable
Pfeil 3.3.4 Mehrere Callable abarbeiten
Pfeil 3.3.5 ScheduledExecutorService für wiederholende Ausgaben und Zeitsteuerungen nutzen
Pfeil 3.4 Synchronisation über kritische Abschnitte
Pfeil 3.4.1 Gemeinsam genutzte Daten
Pfeil 3.4.2 Probleme beim gemeinsamen Zugriff und kritische Abschnitte
Pfeil 3.4.3 Punkte nebenläufig initialisieren
Pfeil 3.4.4 i++ sieht atomar aus, ist es aber nicht *
Pfeil 3.4.5 Kritische Abschnitte schützen
Pfeil 3.4.6 Kritische Abschnitte mit ReentrantLock schützen
Pfeil 3.4.7 Synchronisieren mit synchronized
Pfeil 3.4.8 Synchronized-Methoden der Klasse StringBuffer *
Pfeil 3.4.9 Mit synchronized synchronisierte Blöcke
Pfeil 3.4.10 Dann machen wir doch gleich alles synchronisiert!
Pfeil 3.4.11 Lock-Freigabe im Fall von Exceptions
Pfeil 3.4.12 Deadlocks
Pfeil 3.4.13 Mit synchronized nachträglich synchronisieren *
Pfeil 3.4.14 Monitore sind reentrant – gut für die Geschwindigkeit *
Pfeil 3.4.15 Synchronisierte Methodenaufrufe zusammenfassen *
Pfeil 3.5 Synchronisation über Warten und Benachrichtigen
Pfeil 3.5.1 Die Schnittstelle Condition
Pfeil 3.5.2 It’s Disco-Time *
Pfeil 3.5.3 Warten mit wait(…) und Aufwecken mit notify()/notifyAll() *
Pfeil 3.5.4 Falls der Lock fehlt – IllegalMonitorStateException *
Pfeil 3.6 Datensynchronisation durch besondere Concurrency-Klassen *
Pfeil 3.6.1 Semaphor
Pfeil 3.6.2 Barrier und Austausch
Pfeil 3.6.3 Stop and go mit Exchanger
Pfeil 3.7 Atomare Operationen und frische Werte mit volatile *
Pfeil 3.7.1 Der Modifizierer volatile bei Objekt-/Klassenvariablen
Pfeil 3.7.2 Das Paket java.util.concurrent.atomic
Pfeil 3.8 Teile und herrsche mit Fork und Join *
Pfeil 3.8.1 Algorithmendesign per »teile und herrsche«
Pfeil 3.8.2 Nebenläufiges Lösen von D&C-Algorithmen
Pfeil 3.8.3 Fork und Join
Pfeil 3.9 CompletionStage und CompletableFuture *
Pfeil 3.10 Mit dem Thread verbundene Variablen *
Pfeil 3.10.1 ThreadLocal
Pfeil 3.10.2 InheritableThreadLocal
Pfeil 3.10.3 ThreadLocalRandom als schneller nebenläufiger Zufallszahlengenerator
Pfeil 3.10.4 ThreadLocal bei der Performance-Optimierung
Pfeil 3.11 Threads in einer Thread-Gruppe *
Pfeil 3.11.1 Aktive Threads in der Umgebung
Pfeil 3.11.2 Etwas über die aktuelle Thread-Gruppe herausfinden
Pfeil 3.11.3 Threads in einer Thread-Gruppe anlegen
Pfeil 3.11.4 Methoden von Thread und ThreadGroup im Vergleich
Pfeil 3.12 Zeitgesteuerte Abläufe
Pfeil 3.12.1 Die Typen Timer und TimerTask
Pfeil 3.12.2 Job-Scheduler Quartz
Pfeil 3.13 Einen Abbruch der virtuellen Maschine erkennen
Pfeil 3.13.1 Shutdown-Hook
Pfeil 3.13.2 Signale
Pfeil 3.14 Zum Weiterlesen
 
Zum Seitenanfang

3.6Datensynchronisation durch besondere Concurrency-Klassen * Zur vorigen ÜberschriftZur nächsten Überschrift

Arbeiten mehrere Threads zusammen, so wollen sie in der Regel Daten austauschen und sich an bestimmten Bedingungen synchronisieren. Die Java-API bietet für diese Zusammenarbeit eine Reihe von Klassen:

  • Semaphore: Erlaubt eine maximale Anzahl Threads in einem Programmblock.

  • CyclicBarrier: Eine Menge von Threads wartet aufeinander, um zu einem gemeinsamen Punkt zu kommen.

  • CountDownLatch: Ein oder mehrere Threads warten auf eine Bedingung. Ist sie erfüllt, können die Threads fortfahren.

  • Exchanger: Zwei Threads treffen sich und tauschen Daten aus.

 
Zum Seitenanfang

3.6.1Semaphor Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Semaphor[ 36 ](Semaphoren wurden 1968 vom niederländischen Informatiker Edsger Wybe Dijkstra eingeführt, also 10 Jahre vor Hoares Monitoren.) stellt sicher, dass nur eine bestimmte Anzahl von Threads auf ein Programmstück zugreift, und zählt damit zur Technik der Sperrmechanismen. Es lassen sich zwei Typen von Semaphoren unterscheiden:

  • Binäre Semaphoren lassen höchstens einen Thread auf ein Programmstück zu. Das bekannte Paar await(…)/signalXXX() bei Condition bzw. wait(…)/notifyXXX() von Object bietet sich für binäre Semaphoren an.

  • Allgemeine Semaphoren erlauben eine begrenzte Anzahl an Threads in einem kritischen Abschnitt. Das Semaphor verwaltet intern eine Menge so genannter Erlaubnisse (engl. permits).

Die Klasse Semaphore

Für allgemeine Semaphoren mit einer maximalen Anzahl Threads im Programmstück deklariert die Java-Bibliothek die Klasse java.util.concurrent.Semaphore.

Die wichtigen Eigenschaften der Semaphore-Klasse sind der Konstruktor und die Methoden zum Betreten und Verlassen des kritischen Abschnitts. Intern vermerkt das Semaphor jedes Betreten und lässt Threads warten, wenn das gesetzte Maximum erreicht ist, bis ein anderer Thread das Programmsegment verlässt.

class java.util.concurrent.Semaphore
implements Serializable
  • Semaphore(int permits)
    Das neue Semaphor, das bestimmt, wie viele Threads in einem Block sein dürfen.

  • void acquire()
    Versucht, in den kritischen Block einzutreten. Wenn der gerade belegt ist, wird gewartet. Vermindert die Menge der Erlaubnisse um eins.

  • void release()
    Verlässt den kritischen Abschnitt und legt eine Erlaubnis zurück.

Allgemeine Semaphoren vereinfachen das Konsumenten-Produzenten-Problem, da eine bestimmte Anzahl von Threads in einem Block erlaubt ist. Die verbleibende Größe des Puffers ist somit automatisch die maximale Anzahl von Produzenten, die sich nebenläufig im Einfügeblock befinden können.

Unser Beispiel soll mit einem Semaphor arbeiten, das nur zwei Threads gleichzeitig in den kritischen Abschnitt lässt:

Listing 3.33com/tutego/insel/thread/concurrent/SemaphoreDemo.java, Teil 1

package com.tutego.insel.thread.concurrent;

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {

static Semaphore semaphore = new Semaphore( 2 );

Der kritische Abschnitt besteht aus zwei Operationen: einer Ausgabe auf dem Bildschirm und einer Wartezeit von 2 Sekunden. Er ist in einem Runnable eingebettet:

Listing 3.34com/tutego/insel/thread/concurrent/SemaphoreDemo.java, Teil 2

static Runnable r = new Runnable() {
@Override public void run() {
while ( true ) {
try {
semaphore.acquire();
try {
System.out.println( "Thread=" + Thread.currentThread().getName() +
", Available Permits=" + semaphore.availablePermits() );
TimeUnit.SECONDS.sleep( 2 );
}
finally {
semaphore.release();
}
}
catch ( InterruptedException e ) {
e.printStackTrace();
Thread.currentThread().interrupt();
break;
}
}
}
};

Der kritische Abschnitt beginnt mit dem acquire() und endet mit release(). Wichtig ist die richtige Ausnahmebehandlung. Fünf Dinge müssen beachtet werden und formen den Quellcode:

  • Das acquire() kann eine InterruptedException auslösen, was eine geprüfte Ausnahme ist. Wir müssen sie folglich behandeln.

  • Da acquire() sowie sleep() eine InterruptedException auslösen können, ist die Frage, wie damit umzugehen ist. Im Prinzip signalisiert die Ausnahme die Bitte, das Warten zu beenden. Das wollen wir berücksichtigen, und wir brechen daher aus der Endlosschleife aus. Details zu der Ausnahme und zur interrupt()-Methode enthält Abschnitt 3.2.8, »Einen Thread höflich mit Interrupt beenden«.

  • Immer dann, wenn ein acquire() erfolgreich war, muss auch ein release() folgen. Das release() ist im finally sehr gut aufgehoben, denn wir wollen in jedem Fall die Semaphoren wieder freigeben, auch wenn irgendwie eine andere RuntimeException auftauchen sollte.

  • Ein release() darf nur dann erfolgen, wenn es ein zugehöriges acquire() gibt. Eine Programmierung wie try { semaphore.acquire(); … } finally { semaphore.release(); } ist unsicher, denn wenn ein acquire() wirklich eine Ausnahme erzeugt, wird fälschlicherweise ein release() ausgelöst.

  • Ein release() erzeugt keine Ausnahme, daher muss auch nichts behandelt werden.

Drei Threads sollen sich koordinieren:

Listing 3.35com/tutego/insel/thread/concurrent/SemaphoreDemo.java, Teil 3

public static void main( String[] args ) {
new Thread( r ).start();
new Thread( r ).start();
new Thread( r ).start();
}
}

Nach dem Starten ist gut zu beobachten, wie jeweils zwei Threads im Abschnitt sind (eine Leerzeile symbolisiert die Wartezeit):

Thread=Thread-0, Available Permits=1
Thread=Thread-1, Available Permits=0

Thread=Thread-2, Available Permits=0
Thread=Thread-0, Available Permits=0

Thread=Thread-2, Available Permits=0
Thread=Thread-0, Available Permits=0

Fair und unfair

In der Ausgabe ist zu sehen, dass Thread 0, 1 und 2 zwar ihre Aufgaben ausführen können, aber plötzlich eine Sequenz 0, 2, 0 entsteht. Unser Gerechtigkeitssinn sagt uns jedoch, dass Thread 1 wieder an die Reihe kommen müsste. Wie ist das möglich? Die Antwort lautet, dass das acquire() nicht berücksichtigt, wer am längsten wartet, sondern dass es sich aus der Liste der Wartenden einen beliebigen Thread auswählt (wir kennen das von notify() her und dem Betreten eines synchronized-Blocks). Um ein faires Verhalten zu realisieren, wird die Fairness einfach über den Konstruktor von Semaphore angegeben. Ändern wir im Programm folgende Zeile:

static Semaphore semaphore = new Semaphore( 2, true );

Nun bekommen wir eine Ausgabe wie die folgende:

Thread=Thread-0, Available Permits=1
Thread=Thread-1, Available Permits=0
Thread=Thread-2, Available Permits=0
Thread=Thread-0, Available Permits=0
Thread=Thread-1, Available Permits=0
Thread=Thread-2, Available Permits=0
Thread=Thread-0, Available Permits=0
Thread=Thread-1, Available Permits=0
 
Zum Seitenanfang

3.6.2Barrier und Austausch Zur vorigen ÜberschriftZur nächsten Überschrift

Der Punkt, an dem alle Threads zusammenkommen und sich die Ergebnisse zusammenlegen lassen, heißt auf Englisch barrier. Die Klasse CyclicBarrier im Paket java.util.concurrent realisiert eine solche Barriere. Der Vorteil gegenüber join(…) besteht in der Tatsache, dass der für die Abarbeitung verwendete Thread nicht enden muss – und ein Thread im Thread-Pool endet eigentlich nicht –, sondern dass er mit awaitXXX(…) signalisiert, dass die Arbeit erledigt ist und er auf neue Arbeit wartet. Das folgende Beispiel zeigt anhand eines nebenläufigen Summierers die Funktionsweise:

Listing 3.36com/tutego/insel/thread/concurrent/ArraySummer.java, ArraySummer

public class ArraySummer {

public static void main( String[] args ) {
int[] array = new int[ 1000 ];

Random r = new Random();
for ( int i = 0; i < array.length; i++ )
array[ i ] = Math.abs( r.nextInt() / 2 );

parallSummer( array );
}

public static void parallSummer( int[] array ) {
int prozessors = 2; // Runtime.getRuntime().availableProcessors();

final List<Long> longs = new ArrayList<>();

Runnable merger = new Runnable() {
@Override public void run() {
long sum = 0;
for ( long i : longs )
sum += i;
System.out.println( sum );
}
};

CyclicBarrier barrier = new CyclicBarrier( prozessors, merger );

for ( int part = 0; part < prozessors; part++ )
new Thread( new AtomarSummer( barrier, array, prozessors, part,
longs)).start();
}
}

Listing 3.37com/tutego/insel/thread/concurrent/ArraySummer.java, AtomarSummer

class AtomarSummer implements Runnable {

private final CyclicBarrier barrier;
private final int[] array;
private final List<Long> longs;
private int start, end;

public AtomarSummer( CyclicBarrier barrier, int[] array, int maxPart,
int currentPart, List<Long> longs ) {
this.barrier = barrier;
this.array = array;
this.longs = longs;

start = (int) ((double) array.length / maxPart * currentPart);
end = (int) ((double) array.length / maxPart * (currentPart + 1) – 1);
}

@Override public void run() {
long sum = 0;

for ( int i = start; i < end; i++ )
sum += array[ i ];

longs.add( sum );

try {
barrier.await();
}
catch ( InterruptedException | BrokenBarrierException e ) {
e.printStackTrace();
}
}
}
 
Zum Seitenanfang

3.6.3Stop and go mit Exchanger Zur vorigen ÜberschriftZur nächsten Überschrift

Die generische Klasse java.util.concurrent.Exchanger dient ebenfalls dem Zusammenkommen von Threads, die jedoch bei ihrem Rendezvous Daten austauschen können. Ein üblicher Fall betrifft das Füllen von Puffern, etwa wenn ein Thread Daten vom Datensystem liest und ein anderer Thread die Daten über das Netzwerk weiterschickt. Ein Dateisystem-Thread füllt den Puffer, und wenn er komplett gefüllt ist, trifft er sich mit einem anderen Netzwerk-Thread, dem er den vollen Puffer gibt und von dem er wieder einen leeren Puffer empfängt. Der Netzwerk-Thread kann dann den Inhalt des Puffers wieder »verbrauchen« und der erste Thread den Puffer wieder mit Daten vom Dateisystem füllen.

 


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 SE 8 Standard-Bibliothek Java SE 8 Standard-Bibliothek
Jetzt Buch bestellen

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel
Java ist auch eine Insel


Zum Rheinwerk-Shop: Professionell entwickeln mit Java EE 8
Professionell entwickeln mit Java EE 8


Zum Rheinwerk-Shop: Besser coden
Besser coden


Zum Rheinwerk-Shop: Entwurfsmuster
Entwurfsmuster


Zum Rheinwerk-Shop: IT-Projektmanagement
IT-Projektmanagement


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2018
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.

 
Nutzungsbestimmungen | Datenschutz | Impressum

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

Cookie-Einstellungen ändern