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.

Starten des Programms

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

Man kann diese jar Datei mit

java -jar Treading.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 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 static ThreadFenster tg;
public boolean stop = false;
public boolean synchron = true;

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

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

public class ThreadFenster {

private JFrame hf;
private JButton okButton;
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 {

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 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() {
@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 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 Treads
* @param g
*/
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 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
//String wertThread = k.toString(); // Wert als 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.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);
}
}


 



book | by Dr. Radut