2.9 Kommunikation zwischen Threads mit Piped-Streams

Die Ein- und Ausgabe wird in Java mit Hilfe sogenannter Streams realisiert. Einen Stream kann man sich als eine Art Pipeline vorstellen (siehe Abb. 2.3). Am "Schreibende" wird der Stream mit Daten gefüllt und am "Leseende" werden diese Daten in der selben Reihenfolge wieder herausgeholt. Es können dabei keine Daten verloren gehen, da sie bis zum Auslesen in der Pipe verweilen. Streams sind in Java immer unidirektional.


Abb. 2.3: Die Arbeitsweise von Streams [MiSi1999]

Das Paket java.io stellt dem Programmierer verschiedene Klassen zur Verfügung, die auf dem Streamkonzept basieren.

In dieser Diplomarbeit, in der es um die Verwendung von Threads bzw. Prozessen geht, werden die Piped-Streams, die für einen Datenaustausch zwischen Threads geeignet sind, eingehender besprochen. Pipes werden auch in Windows NT und Unix eingesetzt. Die genaue Arbeitsweise ist dabei vom jeweiligem System abhänig und wird daher separat beschrieben.

Eine Pipe kann in Java mit Hilfe der Klassen PipedReader und PipedWriter realisiert werden und so die Kommunikation zwischen zwei Threads ermöglichen.

Mit einer Pipe könne Streams direkt miteinander gekoppelt werden. Diese Kopplung der Streams kann auf verschiedene Arten erfolgen:

 

Beispiel für die Kommunikation von zwei Threads über eine Pipe:

class CountingThread extends Thread {
 PipedInputStream pipein;
                                    // Konstruktor des Threads
 public CountingThread(PipedInputStream pipein) {	
	
  this.pipein = pipein;             // Stream Objekt zum Lesen 
 }                                  // der Pipe wird übernommen.

 public void run() {
  	while(true)
  	{
   try {ende=pipein.read(lese,0,1);}
   catch (IOException e) {}         // Pipe wird ausgelesen
   if(ende == -1) break;            // bis -1 zurückgegeben wird.
   ...
  }
  	try {pipein.close();} 
  catch(IOException e){}            // Schließt den Stream
 }
}

class CountingThreadRunnable implements Runnable {
 PipedOutputStream pipeout;
                                          // Konstruktor
 public CountingThreadRunnable(PipedOutputStream pipeout){
 
  this.pipeout = pipeout;        // Stream Objekt zum Schreiben
 }                               // in die Pipe wird übernommen.
 
 public void run(){
  	...
  for(hilf=0;hilf<=10;hilf++)        // 10 mal wird eine Zahl 
  	{                              // in die Pipe geschrieben
   try {pipeout.write(schreibe,0,1);} 
   catch(IOException e){}
  }
  try {pipeout.close();}         // Schließt den Stream
  catch(IOException e){}	
 }
}
 class Pipe {                             // Main-Methode
 public static void main(String[] args) {

    // Erzeugen der Stream- Objekte zum Schreiben und Lesen.
  PipedInputStream pipein = new PipedInputStream();
  PipedOutputStream pipeout = new PipedOutputStream();

  try {pipeout.connect(pipein);}      // Kopplung der Streams
  catch (IOException e) {}

    // Erzeugung der Threads mit Übergabe der Stream-Objekte.
  Runnable rthread = new CountingThreadRunnable(pipeout); 
  Thread thread2 = new Thread(rthread); 
  CountingThread thread1 = new CountingThread(pipein);

  thread1.start();         // Starten der Threads
  thread2.start();
 }
}

Die Methoden bzw. Konstruktoren der beiden Klassen PipedReader und PipedWriter werden hier genauer erläutert [MiSi1999]:

Die Klasse PipedReader:

 

Konstruktoren:

public PipedReader()

Erzeugt einen neuen PipedReader, der noch keine Verbindung besitzt. Vor der Benutzung muß er noch mit einem PipedWriter verbunden werden (entweder mit connect() oder dem Konstruktor von PipedWriter ).

public PipedReader(PipedWriter src)

Erzeugt einen neuen PipedReader, der mit dem PipedWriter src verbunden ist.

 

Methoden:

close

public void close()
Schließt den Stream.

connect

public void connect(PipedWriter src)
Verbindet den Stream mit src.

read

public int read(char[] b, int off, int len)
Versucht, len Zeichen aus dem Stream zu lesen und speichert sie ab dem Index off in b. Wenn beim Versuch, das erste Zeichen zu lesen, das Dateiende bereits erreicht ist, ist der Rückgabewert -1. Ansonsten wird die Anzahl der tatsächlich gelesenen Bytes zurückgeliefert. Wenn während des Lesevorgangs das Stream-Ende erreicht wird oder wenn im darunterliegendem Stream nur weniger als gelesen werden können, ohne zu blockieren, kehrt die Methode zurück len Bytes und liefert die Anzahl der bis dahin gelesenen Bytes.

 

Die Klasse PipedWriter:

 

Konstruktoren:

public PipedWriter()

Erzeugt einen neuen PipedWriter, der noch keine Verbindung besitzt. Vor der Benutzung muß er noch mit einem PipedReader verbunden werden (entweder mit connect() oder dem Konstruktor von PipedReader ).

public PipedWriter(PipedReader sink)

Erzeugt einen neuen PipedWriter, der mit dem PipedReader sink verbunden ist.

 

Methoden:

close

public void close()
Schließt den Stream. Falls der Stream bereits geschlossen war, bleibt die Methode ohne Auswirkungen.

connect

public void connect(PipedReader sink)
Verbindet den Stream mit
sink.

flush

public void flush()
Bewirkt, daß noch gepufferte Daten in den Stream geschrieben werden.

write

public void write(char[] b, int off, int len)
Schreibt die ersten
len-Zeichen ab dem Index off aus dem Array b in den Stream.