/*************************************************************/
/* Prof. Dr. Carsten Vogt                                    */
/* FH Koeln, Fak. 07 / Nachrichtentechnik                    */
/* http://www.nt.fh-koeln.de/vogt                            */
/*                                                           */
/* Lösung des Erzeuger-Verbraucher-Problems mit Semaphoren   */
/*************************************************************/

import java.util.concurrent.*;

// grundlegende Werte für die Simulation

class Basiswerte {
  static int gesamtdauer = 20000;    // Gesamtlaufzeit des Programms (in ms)
  static int erzeugungsdauer = 1000; // Zeitdauer zum Erzeugen eines Werts (außerhalb des kritischen Abschnitts)
  static int verbrauchsdauer = 5000; // Zeitdauer zum Verbrauchen eines Werts (außerhalb des kritischen Abschnitts)
  static int schreibdauer = 1000;    // Zeitdauer zum Schreiben eines Werts in den Puffer (im kritischen Abschnitt)
  static int lesedauer = 1000;       // Zeitdauer zum Lesen eines Werts aus dem Puffer (im kritischen Abschnitt)
  static int pufferkapazitaet = 3;   // Kapazität des Puffers
}


// Pufferspeicher

class Puffer {

  // Attribute

  private int inhalt[];  // Werte, die im Puffer gespeichert sind (Array wird als Ringpuffer benutzt)
  private int kap;       // Kapazität = Anzahl der Werte, die der Puffer maximal aufnehmen kann
  private int leseindex, schreibindex; // Index bzgl. 'inhalt[]', mit dem die nächste Lese- bzw. Schreiboperation stattfindet
  
  Semaphore voll, leer;  // Semaphoren, die blockieren, wenn der Puffer voll bzw. leer ist.
  Semaphore wA;          // Semaphor, der den wechselseitigen Ausschluss bei Pufferzugriffen sicherstellt.

  // Konstruktor

  Puffer() {
   kap = Basiswerte.pufferkapazitaet;
   inhalt = new int[kap+1];       // Array hat einen Platz mehr als die Kapazität, um die Fälle "Puffer ist leer" ('leseindex'=='schreibindex')
                                  // und "Puffer ist voll" ('schreibindex' steht eine Position hinter 'leseindex') unterscheiden zu können
   leseindex = schreibindex = 0;
   voll = new Semaphore(kap);  // Der Puffer ist zu Beginn leer => Der 'voll'-Semaphor kann 'kap'-mal heruntergezählt werden, bevor er blockiert
   leer = new Semaphore(0);    // Der Puffer ist zu Beginn leer => Der 'leer'-Semaphor kann 0-mal heruntergezählt werden, bevor er blockiert
   wA = new Semaphore(1);      // wechselseitiger Ausschluss
  }
  
  // 'schreibe()' schreibt den Wert 'wert' in den Puffer (wechselseitig ausgeschlossen zu 'lies()')
  
  void schreibe(int wert) {
  
    // blockiere, wenn der Puffer voll ist

    try { voll.acquire(); } catch (Exception e) { };

    // belege den Puffer (wechselseitiger Ausschluss)

    try { wA.acquire(); } catch (Exception e) { };

    System.out.println("Schreiben beginnt");
    
    // schreibe Wert in den Puffer und setze 'schreibindex' zyklisch um 1 weiter

    try { Thread.sleep(Basiswerte.schreibdauer); } catch (Exception e) { }
    inhalt[schreibindex] = wert;
    schreibindex = (schreibindex+1)%(kap+1);
    
    System.out.println("  "+wert+" geschrieben");
    ausgabe();
    System.out.println("Schreiben endet");
    
    // entblockiere Thread (konkret: Verbraucher-Thread, der möglicherweise in 'lies()' wartet)
    
    leer.release();

    // gib Puffer frei (wechselseitiger Ausschluss)

    wA.release();

  } // Ende 'schreibe()'

  // 'lies()' liest einen Wert aus dem Puffer und liefert ihn zurück (wechselseitig ausgeschlossen zu 'schreibe()')

  synchronized int lies() {

    // blockiere, wenn der Puffer leer ist

    try { leer.acquire(); } catch (Exception e) { };

    // belege den Puffer (wechselseitiger Ausschluss)

    try { wA.acquire(); } catch (Exception e) { };

    System.out.println("Lesen beginnt");

    // schreibe Wert in den Puffer und setze 'leseindex' zyklisch um 1 weiter

    try { Thread.sleep(Basiswerte.lesedauer); } catch (Exception e) { }
    int gelesen = inhalt[leseindex];
    leseindex = (leseindex+1)%(kap+1);
    System.out.println("  "+gelesen+" gelesen");
    ausgabe();
    System.out.println("Lesen endet");

    // entblockiere wartenden Thread (konkret: Erzeuger-Thread, der möglicherweise in 'schreibe()' wartet)

    voll.release();
    
    // gib Puffer frei (wechselseitiger Ausschluss)

    wA.release();

    return gelesen;

  } // Ende 'lies()'

  // 'ausgabe()' gibt den Pufferinhalt auf den Bildschirm aus

  void ausgabe() {
    if (leseindex==schreibindex) {
      System.out.println("  Puffer leer");
      return;
    }
    System.out.print("  Pufferinhalt:");
    if (leseindex<schreibindex)
      for (int i=leseindex;i<schreibindex;i++)
       System.out.print(" "+inhalt[i]);
     else {
      for (int i=leseindex;i<kap+1;i++)
       System.out.print(" "+inhalt[i]);
      for (int i=0;i<schreibindex;i++)
       System.out.print(" "+inhalt[i]);
     }
    System.out.println();
  }
  
  // 'entblockiereThreads()' wird vom Hauptprogramm unmittelbar vor dem 'kill()'-Aufruf aufgerufen,
  // um die zu terminierenden Threads aus einem möglichen Semaphor-Wartezustand aufzuwecken
  
  synchronized void entblockiereThreads() {
    leer.release();
    voll.release();
    wA.release();
  }

} // Ende Puffer


// Verbraucher-Thread: liest Werte aus dem Puffer

class Verbraucher extends Thread {

 // Attribute

 private Puffer puffer; // Puffer, aus dem gelesen wird
 boolean ende;          // wird vom Hauptprogramm auf 'true' gesetzt, um den Thread zu beenden

 // Konstruktor

 Verbraucher(Puffer puffer) {
  this.puffer = puffer;
  ende = false;
 }

 // Schrittfolge des Verbrauchers

 public void run() {

  while (!ende) {
  
   // lies Wert aus dem Puffer
   
   int gelesen = puffer.lies();
   
   // verbrauche den Wert (simuliert durch Schlafen für eine bestimmte Zeit)
   
   try { Thread.sleep(Basiswerte.verbrauchsdauer); } catch (Exception e) { }
   
  }

  System.out.println("Verbraucher terminiert");

 }

 // 'kill()' wird vom Hauptprogramm aufgerufen, um den Thread zu beenden

 void kill() {
   ende = true;
 }

} // Ende Verbraucher


// Erzeuger-Thread: schreibt Werte in einen Puffer

class Erzeuger extends Thread {

 // Attribute

 private Puffer puffer;
 int wert;
 boolean ende;

 // Konstruktor

 Erzeuger(Puffer puffer) {
  this.puffer = puffer;
  wert = 0;
  ende = false;
 }

 // Schrittfolge des Erzeugers
  
 public void run() {

  while (!ende) {
  
   // erzeuge einen Wert (simuliert durch Schlafen für eine bestimmte Zeit)
   
   wert +=10;
   try { Thread.sleep(Basiswerte.erzeugungsdauer); } catch (Exception e) { }
   if (ende) break;
   
   // schreibe den Wert in den Puffer
   
   puffer.schreibe(wert);
   
  }

  System.out.println("Erzeuger terminiert");

 }
 
 // 'kill()' wird vom Hauptprogramm aufgerufen, um den Thread zu beenden

 void kill() {
   ende = true;
 }

} // Ende Erzeuger


// Hauptprogramm

public class ErzeugerVerbraucherMitSemaphor {

 public static void main(String[] args) {
 
  // erzeuge den Puffer und die Threads
  
  Puffer puffer = new Puffer();
  Erzeuger erzeuger = new Erzeuger(puffer);
  Verbraucher verbraucher = new Verbraucher(puffer);

  // starte die Threads

  erzeuger.start();
  verbraucher.start();
  
  // blockiere für die gewünschte Programmlaufzeit

  try { Thread.sleep(Basiswerte.gesamtdauer); } catch (Exception e) { }
  
  // terminiere die Threads
  
  erzeuger.kill();
  verbraucher.kill();
  puffer.entblockiereThreads();

  System.out.println("Programm terminiert");

 }

}

