Beispiel: Kritischer Pfad

Das hier benutzte Beispielprogramm visualiert 15 Threads die sich auf einem synchronisierten Objekt serialisieren.

Threads in grüner Farbe befinden sich nicht im kritischen Pfad. Threads in roter Farbe befinden sich im kritischen Pfad.

Der kritische Pfad in in der Klasse EinMonitor in der Methode buchen() implementiert.

Die einzige Instanz von EinMonitor verfügt über zwei Variablen a und b die Konten darstellen sollen.

Die Methode buchen() "verschiebt" mehrfach einen Betrag zwischen den beiden Konten. Die Summe der beiden Variablen sollte am Ende der Methode stets gleich sein.

Die Methode buchen() enthält etwas Overhead zur Visualierung des Konzepts:

  • am Anfang und am Ende  muss das GUI Subsystemen über den Eintritt und das Verlassen des kritischen Pfads informiert werden
  • zwischen allen Buchungen werden kleine Schlafpausen eingelegt um die Zeit im kritischen Pfad künstlich zu verlängern

Die Methode buchen() enthält eine Konsistenzprüfung am Ende der Methode die bei einem Fehler in der Buchführung eine Konsolenmeldung ausdruckt. Sie kommt in der auf dieser Seite gezeigten Variante nicht zum Zuge!

Die Klasse MainTest dient zum Starten des Programms. Sie erzeugt und startet die 15 Threads. Jeder Thread führt nur eine gewisse Anzahl von Buchungen durch und beendet sich dann.

Aufgaben

  • Übersetzen Sie die Klassen und starten Sie das Hauptprogramm
  • Der Schieberegler erlaubt das Einstellen der Schlafpausen im kritischen Pfad. Was geschieht wenn der kritische Pfad verkürzt wird?
  • Entfernen die das Schlüsselwort synchronized in der Methode buchen(). Was geschieht?
  • Was würde geschehen geschehen wenn die künstlichen sleep Aufrufe entfernt werden?
    • Hinweis: Sie müssen dann auch die Anzahl der Durchläufe pro Thread stark erhöhen. Da die Zeit im kritischen Pfad sehr kurz wird.
  • Was geschieht wenn man den yield() Aufruf für in der run() Methode von MainTest entfernt

Kommentar

Da die aktuelle Ausführungsgeschwindigkeit, 4-5 Zehnerpotenzen, jenseits der menschlichen Wahrnehmungsfähigkeit liegt ist, es sehr schwer die echten Abläufe im Zeitlupentempo zu visualisieren. Ein künstlicher sleep() Aufruf blockiert den Prozess und gibt den Prozessor an das Betriebssystem zurück. Der Scheduler des Betriebssystems trifft bei dieser künstlichen Verlangsamung eventuell andere Entscheidungen in Bezug auf den Thread den er ausführt. Das gleiche Problem besteht beim Debuggen von Javaprogrammen. Durch das Bremsen bestimmter Threads können existierende Fehler nicht mehr reproduzierbar sein oder bisher nicht aufgetretene Fehler in der Synchronsiation können sichtbar werden.

Starten des Programms

Die benötigten Klassen sind in Threading.jar zusammen gefaßt.

Man kann diese jar Datei mit

java -jar Threading.jar

von der Kommandozeile nach dem Runterladen starten. Vielleicht reicht auch ein Doppelklick auf die Datei im Download-Ordner...

Nach dem Übersetzen der Dateien oder nach dem Starten der jar-Datei erscheint ein GUI wie es im folgenden Bild zu sehen ist:

Klasse MainTest

Hauptprogramm der Anwendung.

package s2.thread;

/**
*
* @author s@scalingbits.com
*/
public class MainTest extends Thread {

public static final int INCRITICALPATH = 0;
public static final int NOTINCRITICALPATH = 1;
public static final int ENDED = 2;
public static int anzahlThreads = 15;
public static MainTest[] mt;
public int threadStatus = NOTINCRITICALPATH;
private static EinMonitor myMonitor;
public static int sleepPeriod = 500;
public int meineID;
public static ThreadingPanel tp;
public static ThreadFenster tg;
public boolean stop = false;
public boolean synchron = true;

   public MainTest(int id) {
      meineID = id;
   }
   @Override
   public void run() {
      long anfangszeit = System.nanoTime();
      System.out.println("Thread [" + meineID + "] gestartet");
      //GUIupdate(NOTINCRITICALPATH);
      for (long i = 0; i < 200; i++) {
         Thread t = Thread.currentThread();
         // Erlaube anderen Threads die CPU zu holen
         t.yield();
      if (tg.synchron)
         myMonitor.buchen(10);
      else
          myMonitor.parallelbuchen(10);
      }
      threadStatus = ENDED;
      System.out.println("Thread [" + meineID + "] beendet...");
   }
 
   public static void main(String[] args) {
      // Anlegen des Monitorobjekts
      myMonitor = new EinMonitor(1000000L);
      mt = new MainTest[anzahlThreads];
      tg = new ThreadFenster();
      tp = tg.tp;
      // Erzeuge die Threads
      for (int i = 0; i < anzahlThreads; i++) {
         mt[i] = new MainTest(i);
      }
      // Starte die Threads
      for (int i = 0; i < anzahlThreads; i++) {
         mt[i].start();
      }
   }
}

Klasse EinMonitor

package s2.thread;

/**
*
* @author s@scalingbits.com
*/
public class EinMonitor {
   long invariante;
   long a;
   long b;

   public EinMonitor(long para) {
      invariante = para;
      a = para;
      b = 0L;
   }

   synchronized public void buchen(long wert) {
      GUIupdate(MainTest.INCRITICALPATH);
      sleepABit(MainTest.sleepPeriod/5);
      this.a = this.a - wert;
      sleepABit(MainTest.sleepPeriod/5);
      this.b = this.b + wert;
      sleepABit(MainTest.sleepPeriod/5);
      this.a = this.a + wert;
      sleepABit(MainTest.sleepPeriod/5);
      this.b = this.b - wert;
      sleepABit(MainTest.sleepPeriod/5);
      GUIupdate(MainTest.NOTINCRITICALPATH);

      if ((a+b) != invariante)
         System.out.println("Inkonsistenter Zustand");
      }

   public void parallelbuchen(long wert) {
      GUIupdate(MainTest.INCRITICALPATH);
      sleepABit(MainTest.sleepPeriod/5);
      this.a = this.a - wert;
      sleepABit(MainTest.sleepPeriod/5);
      this.b = this.b + wert;
      sleepABit(MainTest.sleepPeriod/5);
      this.a = this.a + wert;
      sleepABit(MainTest.sleepPeriod/5);
      this.b = this.b - wert;
      sleepABit(MainTest.sleepPeriod/5);
      GUIupdate(MainTest.NOTINCRITICALPATH);

      if ((a+b) != invariante)
      System.out.println("Inkonsistenter Zustand");
   }

   private void sleepABit(int sleep) {
      try {
          Thread.sleep(sleep);
      } catch (InterruptedException e) {}
   }

   private void GUIupdate(int status) {
      MainTest t = (MainTest) Thread.currentThread();
      t.threadStatus = status;
      t.tp.repaint();
   }
}

Klasse ThreadFenster

package s2.thread;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
*
* @author s@scalingbits.com
*
*/
public class ThreadFenster {
   final private JFrame hf;
   private JButton okButton;
   final private JButton exitButton;
   JTextField threadDisplay;
   private final static int SLEEPMIN = 1;
   private final static int SLEEPMAX = 2000;
   private final static int SLEEPINIT = 500;
   private final int threadCurrent = 10;
   public ThreadingPanel tp;
   public boolean synchron = true;
   JRadioButton syncButton;
   JRadioButton nosyncButton;

   public class exitActionListener implements ActionListener {
      @Override
      public void actionPerformed(ActionEvent e) {
         System.exit(0);
      }
   }
   /**
   * Aufbau des Fensters zur Ausnahmebehandlung
   *
   */
   public ThreadFenster() {
      JPanel buttonPanel;
      // Erzeugen einer neuen Instanz eines Swingfensters
      hf = new JFrame("Thread Monitor");
     //Nicht Beenden bei Schliesen des Fenster
     hf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     // Anlegen der Buttons
     exitButton = new JButton("Beenden");
     JLabel threadsLabel = new JLabel("sleep(ms):");
     JSlider threadSlider = new JSlider
        (JSlider.HORIZONTAL, SLEEPMIN, SLEEPMAX, SLEEPINIT);
     threadDisplay = new JTextField();
     threadDisplay.setText(Integer.toString(threadCurrent));
     threadDisplay.setColumns(4);
     threadDisplay.setEditable(false);
     threadSlider.addChangeListener(new ChangeListener() {
        @Override
        public void stateChanged(ChangeEvent e) {
           JSlider source = (JSlider) e.getSource();
           if (!source.getValueIsAdjusting()) {
               MainTest.sleepPeriod = source.getValue();
               threadDisplay.setText(Integer.toString(MainTest.sleepPeriod));
           }
        }
      });
      exitButton.addActionListener(new exitActionListener());
      syncButton = new JRadioButton("Synchronisiert");
      syncButton.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
            synchron= true;
            System.out.println("Synchronisiert");
         }
      } );
      syncButton.setSelected(true);
      nosyncButton = new JRadioButton(" Nicht Sync.");
      nosyncButton.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
             synchron= false;
             System.out.println("Nicht synchronisiert");
        }
      } );
      ButtonGroup group = new ButtonGroup();
      group.add(syncButton);
      group.add(nosyncButton);
      JPanel syncPanel = new JPanel();
      BoxLayout bl = new BoxLayout(syncPanel, BoxLayout.Y_AXIS);
      syncPanel.setLayout(bl);
      syncPanel.add(syncButton);
      syncPanel.add(nosyncButton);
     //Aufbau des Panels
     //buttonPanel = new JPanel(new GridLayout(1, 0));
     buttonPanel = new JPanel();
     buttonPanel.add(threadsLabel);
     buttonPanel.add(threadSlider);
     buttonPanel.add(threadDisplay);
     //buttonPanel.add(okButton);
     buttonPanel.add(syncPanel);
     buttonPanel.add(exitButton);
     tp = new ThreadingPanel();
     // Aubau des ContentPanes
     Container myPane = hf.getContentPane();
     myPane.add(buttonPanel, BorderLayout.SOUTH);
     myPane.add(tp, BorderLayout.CENTER);  
     JMenuBar jmb = new JMenuBar();
     JMenu jm = new JMenu("Ablage");
     jmb.add(jm);
     JMenuItem jmi = new JMenuItem("Beenden");
     jmi.addActionListener(new exitActionListener());
     jmi.setEnabled(true);
     jm.add(jmi);
     hf.setJMenuBar(jmb);
     //Das JFrame sichtbar machen
     hf.pack();
     hf.setVisible(true);
     hf.setAlwaysOnTop(true);
   }
}

Klasse ThreadingPanel

package s2.thread;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
/**
*
* @author s@scalingbits.com
*/
public class ThreadingPanel extends JPanel {

   private final int ziffernBreite = 10; // Breite einer Ziffer in Pixel
   private final int ziffernHoehe = 20; // Hoehe einer Ziffer in Pixel

   public ThreadingPanel() {
      setPreferredSize(new Dimension(200, 100));
      setDoubleBuffered(true);
   }
   /**
   * Methode die das Panel überlädt mit der Implementierung
   * der Treads
   * @param g
   */
   @Override
   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      int maxWidth = getWidth();
      int maxHeight = getHeight();
      g.setColor(Color.black);
      g.drawString("Anzahl threads: " + MainTest.anzahlThreads, 10, 20);
      for (int i = 0; i < MainTest.anzahlThreads; i++) {
         paintThread(g, i, 20 + 25 * i, 30);
      }
   }
   /**
   * Malen eines Threads und seines Zustands
   * @param g Graphicshandle
   * @param id Identifier
   * @param x X Koordinate des Thread
   * @param y Y Koordinate des Thread
   */
   public void paintThread(Graphics g, int id, int x, int y) {
      int xOffset = 1; // offset Box zu Text
      int yOffset = 7; // offset Box zu Text
      //String wertThread = k.toString(); // Wert als Text

     if (MainTest.mt[id] != null) {
        switch(MainTest.mt[id].threadStatus) {
           case MainTest.ENDED:             g.setColor(Color.LIGHT_GRAY); break;
           case MainTest.NOTINCRITICALPATH: g.setColor(Color.GREEN); break;
           case MainTest.INCRITICALPATH:    g.setColor(Color.RED); break;
           default: assert(true):"Hier laeuft etwas falsch";
       }
   }
   int breite = 2 * ziffernBreite;
   int xNextNodeOffset = 20;
   int yNextNodeOffset = ziffernHoehe * 6 / 5; // Vertikaler Offset zur naechsten Kn.ebene
   //g.setColor(Color.); // Farbe des Rechtecks im Hintergrund
   g.fillRoundRect(x - xOffset, y - yOffset, breite, ziffernHoehe, 3, 3);
   g.setColor(Color.black); // Schriftfarbe
   g.drawString(Integer.toString(id), x + xOffset, y + yOffset);
   }
}