next up previous contents index
Nächste Seite: Ein-/Ausgabe Aufwärts: Programmieren in Java Vorherige Seite: Ausnahmen   Inhalt   Index

Unterabschnitte

Parallelausführung

In den meisten Programmiersprachen arbeitet man mit einzelnen sequentiellen Ausführungssträngen ( single threaded).



\psfig {figure=bilder/sthread.ps}

Realistischer ist oft die Modellierung durch parallele Ausführungsstränge ( multi threaded).



\psfig {figure=bilder/mthread.ps}

Arbeiten parallele Ausführungsstränge auf gemeinsamen Daten, entstehen zufällige Ergebnisse ( race hazards) , wenn die Threads nicht bezüglich dieser Daten synchronisiert werden.

Java bietet die Mechanismen zur Programmierung paralleler Ausführungsstränge:



Starten von Threads

Die Klasse Thread steht in der Bibliothek java.lang zur Verfügung.



Es gibt zwei Arten, Threads zu definieren und zu starten:



1. Möglichkeit



Definiere eine Unterklasse von Thread. Überschreibe deren Methode run. Dann werden Objekte dieser Unterklasse erzeugt und gestartet (Methode start).



Beispiel:




  class PrimThread extends Thread 
  { long minPrim;
    PrimThread(long minPrim) 
    { this.minPrim = minPrim;
    }
    public void run() 
    { // Berechne Primzahlen 
      // größer als minPrim
      . . .
    }
  }



Starten durch:


  PrimThread p = new PrimThread(143);
  p.start();


2. Möglichkeit



Definiere ein Klasse, die das Interface Runnable implementiert. Erzeuge ein Objekt dieser Klasse, übergebe es als Argument der Thread-Erzeugung und starte den erzeugten Thread.



Beispiel:




  class PrimRun implements Runnable 
  { long minPrim;
    PrimRun(long minPrim) 
    { this.minPrim = minPrim;
    }
    public void run() 
    { // Berechne Primzahlen 
      // größer als minPrim
      . . .
    }
  }

Starten durch:


  PrimRun p = new PrimRun(143);
  new Thread(p).start();


Beispiel:




class Uthread extends Thread 
{ public Uthread(String str) 
  { super(str); // Thread hat einen Namen
  }
  public void run() 
  { for (int i = 0; i < 5; i++) 
    { System.out.println(i+" "+getName());
       try 
       { sleep((int)(Math.random()*1000));
       } 
       catch (InterruptedException e) {}
    }
  System.out.println("1998: "+getName());
  }
}

class Urlaub 
{ public static void main (String[] args) 
  { new Uthread("Sauerland").start();
    new Uthread("Malediven").start();
  }
}


Könnte z.B. folgende Ausgabe erzeugen:


   0 Sauerland
   0 Malediven
   1 Sauerland
   2 Sauerland
   3 Sauerland
   1 Malediven
   4 Sauerland
   1998: Sauerland
   2 Malediven
   3 Malediven
   4 Malediven
   1998: Malediven




Wichtiges Anwendungsfeld für Threads sind auch Applets. Sie brauchen Threads, um die Anzeige regelmäßig auffrischen zu können ohne andere Abläufe zu behindern.

Beispiel: Digitaluhr


import java.awt.Graphics;
import java.util.Date;

public class Clock 
extends java.applet.Applet 
implements Runnable 
{ Thread digital;
  public void start() 
  { if (digital == null) 
    { digital = new Thread(this, "Clock");
      digital.start();
    }
  }
  public void run() 
  { while (digital != null) 
    { repaint();
      try { Thread.sleep(1000);} 
      catch (InterruptedException e){}
    }
  }
  public void paint(Graphics g) 
  { g.drawString
        (new Date().toString(), 5, 10);
  }
  public void stop() 
  { digital = null;
  }
}


Zustand eines Threads

\psfig {figure=bilder/thrstate.ps}



Für den Übergang zwischen Runnable und Not Runnable gibt es vier Gründe:



Synchronisation von Threads über Daten

Synchronisation von Threads dient der Sicherstellung des gegenseitigen Ausschlusses beim Zugriff auf gemeinsam benutzte Daten.

Java bietet zwei Konstrukte






Synchronisierte Methoden

Wird eine als synchronized markierte Methode für ein Objekt aufgerufen, so gilt dieses Objekt als gesperrt ( locked).

Alle anderen Threads, die ebenfalls eine synchronisierte Methode für dieses Objekt aufrufen, werden blockiert, bis diese Sperre aufgehoben ist.



So läßt sich gegenseitiger Ausschluß beim Zugriff auf Daten sicherstellen.



Die Java-Synchronisation beruht auf Monitoren (Hoare, 1974) (siehe Seite [*]). Es gibt allerdings keine expliziten lock- und unlock-Operationen.



Beispiel:




class Konto
{ private double Kontostand;
  public Konto(double anf)
  { Kontostand = anf;
  }
  public synchronized 
      double getKontostand()
  { return Kontostand;
  }
  public synchronized 
      void setKontostand(double d)
  { Kontostand = d;
  }
  public synchronized 
      void einzahlen(double b)
  { double a = getKontostand();
    a += b;
    setKontostand(a);
  }
}




Synchronisierte Klassenmethoden

synchronized-Markierung geht auch bei Klassenmethoden :



Überschreiben von synchronisierten Methoden

Eine Methode, die eine synchronisierte Methode überschreibt, kann entweder synchronisiert oder nicht synchronisiert sein.



Die Eigenschaft der überschriebenen Methode, Objekte zu sperren, geht durch die nicht synchronisierte überschreibende Methode nicht verloren.


  class Ober
  { synchonized void method()
    { ...
    }
    ...
  }
  class Unter extends Ober
  { void method()
    { super.method();  // jetzt sperren
      ...
    }
    ...
  }



Synchronisierte Anweisungen

Synchronisierte Anweisungen dienen dazu, bezüglich Objekten zu synchronisieren auch wenn keine synchronisierten Methoden vorhanden sind.



Form:


   synchronized(expr)
      statement

Bedeutung:

Wenn der Thread die Sperre für das Objekt expr erhält, kann die Anweisung statement ausgeführt werden.



Beispiel:




   // numbs soll nach der folgenden
   // Schleife Absolutwerte enthalten
   synchronized (numbs)
   { for (int i = 0; i < arr.length; i++)
       if (numbs[i] < 0)
          numbs[i] = -numbs[i];
   }




Die Sperren, die zur Realisierung synchronisierter Anweisungen verwendet werden, sind die gleichen wie für synchronisierte Methoden.



Die beiden Synchronisations-Schreibweisen von Java können also zusammenarbeiten.




Synchronisation von Threads mit wait und notify

Die bisher betrachteten Objekt-Sperren stellen den gegenseitigen Ausschluß beim Zugriff auf gemeinsam benutzte Daten sicher.



Aktiv zusammenarbeitende Threads müssen zusätzlich über Eigenschaften gemeinsam benutzter Ressourcen informiert sein.





Typisches Beispiel: Produzent/Konsument-Kommunikation




class Produzent extends Thread 
{ private Puffer puffer;

  public Produzent(Puffer p) 
  { puffer = p;
  }

  public void run() 
  { for (int i = 0; i < 5; i++) 
    { puffer.put(i);
    System.out.println
          ("Produzent gibt: " + i);
    try 
      { sleep((int)(Math.random() * 1000));
      } 
    catch (InterruptedException e) {}
    }
  }
}



class Konsument extends Thread
{ private Puffer puffer;

  public Konsument(Puffer p) 
  { puffer = p;
  }

  public void run() 
  { int wert = 0;
    for (int i = 0; i < 5; i++) 
    { wert = puffer.get();
      System.out.println
            ("Konsument nimmt " + wert);
    }
  }
}




Das Hauptprogramm, das Produzent und Konsument startet:


class P_K_Test
{ public static void main(String[] args) 
  { Puffer p = new Puffer();
    new Produzent(p).start();
    new Konsument(p).start();
  }
}


Annahme: Der benutzte Puffer kann genau eine Zahl aufnehmen:



Wenn Produzent und Konsument sich nicht über Eigenschaften der gemeinsam benutzten Ressource vom Typ Puffer verständigen, wird das Ergebnis falsch:






Die Synchronisation zwischen Produzent und Konsument geschieht in Java über Monitore.








Die wait()-Methode

Ein Thread im Monitor kann wait() aufrufen, um den Monitor freizugeben, während er auf eine bestimmte Bedingung wartet.




Die notify()- und notifyAll()-Methoden

Ein Thread im Monitor kann notifyAll() oder notify() aufrufen, um andere Threads, die wegen der gleichen Bedingungsvariable warten, wieder ausführbereit zu machen.



notifyAll macht alle wartenden Threads ausführbereit.

notify wählt aus den wartenden Threads einen aus, der ausführbereit wird.

Der Puffer für das Produzent/Konsument-Schema


class Puffer {
  // Pufferelement zwischen Produzent 
  // und Konsument
  private int inhalt;
  private boolean gefüllt = false;
  
  public synchronized int get() 
  { while (gefüllt == false) 
    { try {wait();} 
      catch (InterruptedException e) {}
    }
    gefüllt = false;
    notifyAll();
    return inhalt;
  }
  
  public synchronized void put(int wert)
  { while (gefüllt == true) 
    { try { wait();} 
      catch (InterruptedException e) {}
    }
    inhalt = wert;
    gefüllt = true;
    notifyAll();
  }
}



next up previous contents index
Nächste Seite: Ein-/Ausgabe Aufwärts: Programmieren in Java Vorherige Seite: Ausnahmen   Inhalt   Index
Peter Pfahler, 1997