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

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