Skip to Content

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.

Klasse MainTest

Hauptprogramm der Anwendung.

package Kurs2.Thread;

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 boolean stop = false;

public MainTest(int id) {
meineID = id;
}

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();
myMonitor.buchen(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];
ThreadFenster 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 Kurs2.Thread;

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");
}

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 Kurs2.Thread;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ThreadFenster {

private JFrame hf;
private JButton okButton;
private JButton exitButton;
JTextField threadDisplay;
private static int SLEEPMIN = 1;
private static int SLEEPMAX = 2000;
private static int SLEEPINIT = 500;
private int threadCurrent = 10;
public ThreadingPanel tp;

public class exitActionListener implements ActionListener {

public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}

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 Button
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() {
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());

//Aufbau des Panels
buttonPanel = new JPanel();
buttonPanel.add(threadsLabel);
buttonPanel.add(threadSlider);
buttonPanel.add(threadDisplay);
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 ThreadPanel

package Kurs2.Thread;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;

/**
*
* @author sschneid
*/
public class ThreadingPanel extends JPanel {

private int ziffernBreite = 10; // Breite einer Ziffer in Pixel
private 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 Baumgraphik
* @param g
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
int maxWidth = getWidth();
int maxHeight = getHeight();
for (int i = 0; i < MainTest.anzahlThreads; i++) {
g.setColor(Color.black);
g.drawString("Anzahl threads: " + MainTest.anzahlThreads, 10, 20);
paintThread(g, i, 20 + 25 * i, 30);
}
}

/**
* Malen eines Threads und seines Zustands
* @param g Graphicshandle
* @param k zu malender Thread
* @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

if (MainTest.mt[id] != null) {
if (MainTest.mt[id].threadStatus == MainTest.ENDED) {
g.setColor(Color.LIGHT_GRAY);
}
if (MainTest.mt[id].threadStatus == MainTest.NOTINCRITICALPATH) {
g.setColor(Color.GREEN);
}
if (MainTest.mt[id].threadStatus == MainTest.INCRITICALPATH) {
g.setColor(Color.RED);
}
}
int breite = 2 * ziffernBreite;
int xNextNodeOffset = 20;
int yNextNodeOffset = ziffernHoehe * 6 / 5; // Vertikaler Offset zur naechsten Kn.ebene
g.fillRoundRect(x - xOffset, y - yOffset, breite, ziffernHoehe, 3, 3);
g.setColor(Color.black); // Schriftfarbe
g.drawString(Integer.toString(id), x + xOffset, y + yOffset);
}
}


 



book | by Dr. Radut