Galileo Computing :: Java 7 - Mehr als eine Insel - 2 Threads und nebenläufige Programmierung
Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

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

Download:
- openbook, ca. 21,3 MB
Buch bestellen
Ihre Meinung?

Spacer
Java 7 - Mehr als eine Insel von Christian Ullenboom
Das Handbuch zu den Java SE-Bibliotheken
Buch: Java 7 - Mehr als eine Insel

Java 7 - Mehr als eine Insel
Galileo Computing
1433 S., 2012, geb.
49,90 Euro, ISBN 978-3-8362-1507-7
Pfeil 2 Threads und nebenläufige Programmierung
Pfeil 2.1 Threads erzeugen
Pfeil 2.1.1 Threads über die Schnittstelle Runnable implementieren
Pfeil 2.1.2 Thread mit Runnable starten
Pfeil 2.1.3 Die Klasse Thread erweitern
Pfeil 2.2 Thread-Eigenschaften und -Zustände
Pfeil 2.2.1 Der Name eines Threads
Pfeil 2.2.2 Wer bin ich?
Pfeil 2.2.3 Die Zustände eines Threads *
Pfeil 2.2.4 Schläfer gesucht
Pfeil 2.2.5 Mit yield() auf Rechenzeit verzichten
Pfeil 2.2.6 Der Thread als Dämon
Pfeil 2.2.7 Das Ende eines Threads
Pfeil 2.2.8 Einen Thread höflich mit Interrupt beenden
Pfeil 2.2.9 UncaughtExceptionHandler für unbehandelte Ausnahmen
Pfeil 2.2.10 Der stop() von außen und die Rettung mit ThreadDeath *
Pfeil 2.2.11 Ein Rendezvous mit join() *
Pfeil 2.2.12 Arbeit niederlegen und wieder aufnehmen *
Pfeil 2.2.13 Priorität *
Pfeil 2.3 Der Ausführer (Executor) kommt
Pfeil 2.3.1 Die Schnittstelle Executor
Pfeil 2.3.2 Die Thread-Pools
Pfeil 2.3.3 Threads mit Rückgabe über Callable
Pfeil 2.3.4 Mehrere Callable abarbeiten
Pfeil 2.3.5 ScheduledExecutorService für wiederholende Ausgaben und Zeitsteuerungen nutzen
Pfeil 2.4 Synchronisation über kritische Abschnitte
Pfeil 2.4.1 Gemeinsam genutzte Daten
Pfeil 2.4.2 Probleme beim gemeinsamen Zugriff und kritische Abschnitte
Pfeil 2.4.3 Punkte parallel initialisieren
Pfeil 2.4.4 i++ sieht atomar aus, ist es aber nicht *
Pfeil 2.4.5 Kritische Abschnitte schützen
Pfeil 2.4.6 Kritische Abschnitte mit ReentrantLock schützen
Pfeil 2.4.7 Synchronisieren mit synchronized
Pfeil 2.4.8 Synchronized-Methoden der Klasse StringBuffer *
Pfeil 2.4.9 Mit synchronized synchronisierte Blöcke
Pfeil 2.4.10 Dann machen wir doch gleich alles synchronisiert!
Pfeil 2.4.11 Lock-Freigabe im Fall von Exceptions
Pfeil 2.4.12 Deadlocks
Pfeil 2.4.13 Mit synchronized nachträglich synchronisieren *
Pfeil 2.4.14 Monitore sind reentrant – gut für die Geschwindigkeit *
Pfeil 2.4.15 Synchronisierte Methodenaufrufe zusammenfassen *
Pfeil 2.5 Synchronisation über Warten und Benachrichtigen
Pfeil 2.5.1 Die Schnittstelle Condition
Pfeil 2.5.2 It’s Disco-Time *
Pfeil 2.5.3 Warten mit wait() und Aufwecken mit notify() *
Pfeil 2.5.4 Falls der Lock fehlt: IllegalMonitorStateException *
Pfeil 2.6 Datensynchronisation durch besondere Concurrency-Klassen *
Pfeil 2.6.1 Semaphor
Pfeil 2.6.2 Barrier und Austausch
Pfeil 2.6.3 Stop and go mit Exchanger
Pfeil 2.7 Atomare Operationen und frische Werte mit volatile *
Pfeil 2.7.1 Der Modifizierer volatile bei Objekt-/Klassenvariablen
Pfeil 2.7.2 Das Paket java.util.concurrent.atomic
Pfeil 2.8 Teile und herrsche mit Fork und Join *
Pfeil 2.8.1 Algorithmendesign per »teile und herrsche«
Pfeil 2.8.2 Paralleles Lösen von D&C-Algorithmen
Pfeil 2.8.3 Fork und Join
Pfeil 2.9 Mit dem Thread verbundene Variablen *
Pfeil 2.9.1 ThreadLocal
Pfeil 2.9.2 InheritableThreadLocal
Pfeil 2.9.3 ThreadLocalRandom als Zufallszahlengenerator
Pfeil 2.9.4 ThreadLocal bei der Performance-Optimierung
Pfeil 2.10 Threads in einer Thread-Gruppe *
Pfeil 2.10.1 Aktive Threads in der Umgebung
Pfeil 2.10.2 Etwas über die aktuelle Thread-Gruppe herausfinden
Pfeil 2.10.3 Threads in einer Thread-Gruppe anlegen
Pfeil 2.10.4 Methoden von Thread und ThreadGroup im Vergleich
Pfeil 2.11 Zeitgesteuerte Abläufe
Pfeil 2.11.1 Die Typen Timer und TimerTask
Pfeil 2.11.2 Job-Scheduler Quartz
Pfeil 2.12 Einen Abbruch der virtuellen Maschine erkennen
Pfeil 2.13 Zum Weiterlesen

Galileo Computing - Zum Seitenanfang

2.6 Datensynchronisation durch besondere Concurrency-Klassen *Zur 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.

Galileo Computing - Zum Seitenanfang

2.6.1 SemaphorZur nächsten ÜberschriftZur vorigen Überschrift

Ein Semaphor[10](Semaphoren wurden 1968 vom niederländischen Informatiker Edsger Wybe Dijkstra eingeführt, also zehn 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()/signal() bei Condition beziehungsweise wait()/notify() 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 sogenannter 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.

Abbildung

Abbildung 2.11: UML-Diagramm für 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 parallel 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 2.31: com/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 zwei Sekunden. Er ist in einem Runnable eingebettet:

Listing 2.32: com/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 2.2.8.
  • 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 2.33: com/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

Galileo Computing - Zum Seitenanfang

2.6.2 Barrier und AustauschZur nächsten ÜberschriftZur vorigen Überschrift

Der Punkt, an dem alle Threads zusammenkommen und sich die Ergebnisse zusammenlegen lassen, heißt auf Englisch barrier. Seit Java 5 gibt es die Klasse CyclicBarrier im Paket java.util.concurrent, die eine solche Barriere realisiert. 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 await() sein »Bin-fertig«-Signal geben kann. Das folgende Beispiel zeigt anhand eines parallelen Summierers die Funktionsweise:

Listing 2.34: com/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<Long>();

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 2.35: com/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 e )
{
e.printStackTrace();
}
catch ( BrokenBarrierException e )
{
e.printStackTrace();
}
}
}

Galileo Computing - Zum Seitenanfang

2.6.3 Stop and go mit ExchangerZur vorigen Ü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.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.







<< zurück
  Zum Katalog
Zum Katalog: Java 7 – Mehr als eine Insel
Java 7 – Mehr als eine Insel
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Java und XML






 Java und XML


Zum Katalog: Einstieg in Eclipse 3.7






 Einstieg in
 Eclipse 3.7


Zum Katalog: Android 3






 Android 3


Zum Katalog: NetBeans Platform 7






 NetBeans Platform 7


Zum Katalog: Java ist auch eine Insel






 Java ist
 auch eine Insel


Zum Katalog: Apps entwickeln für Android 4






 Apps entwickeln
 für Android 4


Zum Katalog: Java 7






 Java 7


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Galileo Press 2012
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das 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