|
(Lizenz) |
EinleitungJava bietet als Programmiersprache von Beginn an eine aktive Unterstützung für das Programmieren mit Threads (engl der Faden, das Fädchen). Hierdurch ist es in Java relativ einfach nebenläufige Programme zu implementieren. |

Hiermit ist die Implementierung von Javaanwendungen mit den folgen Vorteilen möglich:
Mit Java-Threads kann man nebenläufige Programme programmieren dies es erlauben mehrere Dinge gleichzeitig zu tun. Ein sehr einfaches Beispiel ist eine Programm mit einer graphische Benutzeroberfläche. Dieses Programm lädt typischerweise in einem Thread eine Datei aus dem Internet während gleichzeitig ein anderer Thread auf dem Bildschirm einen Fortschrittsbalken vergrößert.
Betriebssysteme verwalten die Ressourcen eines Rechners. Beim Programmieren mit nebenläufigen Java-Threads ist es wichtig zu verstehen, wie die beiden wichtigen Resourcen Hauptspeicher und Prozessoren vom Javalaufzeitsystem und dem Betriebsystem verwaltet werden. Bei der Betrachtung des Speichers spielt nur der virtuelle Speicher des Betriebsystems eine Rolle, da der Javaentwickler in der Regel keinen direkten Einfluss auf dem physischen Speicher nehmen kann. Moderne Betriebsysteme sind in der Lage Programme gleichzeitig bzw. nebenläufig auszuführen.
| Prozess(Informatik) |
|---|
| Ein Prozess bezeichnet in der Informatik ein im Ablauf befindliches Computerprogramm (siehe Wikipedia). Zum Prozess gehört das Programm samt seiner Daten und dem Prozesskontext (siehe Wikipedia). |
Das Javalaufzeitsystem ist aus der Sicht des Betriebssystems während seiner Ausführung ein Prozess.
Prozesse besitzen während ihrer Ausführung typischerweise:
Threads (engl. der Faden, das Fädchen) sind leichtgewichtige Ausführungseinheiten eines Prozesses:
| Thread (Informatik) |
|---|
| Ein Thread (auch: Aktivitätsträger oder leichtgewichtiger Prozess) bezeichnet in der Informatik einen Ausführungsstrang oder eine Ausführungsreihenfolge in der Abarbeitung eines Programms. Ein Thread ist Teil eines Prozesses. (siehe Wikipedia) |
Das Javalaufzeitsystem ist typischerweise ein Prozess des Betriebssystems. Die Java-Threads werden normalerweise auf Betriebssystem-Threads abgebildet. Dies war in frühen Javaimplementierungen (1.1) nicht der Fall. Hier wurden die Threads von Java selbst verwaltet (siehe Green Threads) .
Ein Thread besitzt typischerweise
Prozesse bestehen aus mindestens einem Thread (dem Haupt-Thread, Main-Thread) und eventuell zusätzlichen Threads die eigene Programmstacks zur parallelen Ausführung besitzen. Die Lebensdauer von Threads ist durch die Lebensdauer des dazugehörigen Prozesses beschränkt. Sie enden mit dem Beenden des Pozesses.

Betriebsysteme weisen den Prozessen die Prozessoren zur Ausführung zu. Hierfür verwendet man die englischen Begriffe des "scheduling" oder "dispatching". Ziel des Betriebssystems ist es die Prozessoren möglichst gut auszunutzen und eine faire, vorteilhafte Abarbeitung aller Programme in Prozessen zu gewährleisten.
Die (historisch) einfachste Art der Verwaltung von Prozessen durch das Betriebsystem ist der Batchbetrieb (Stapelbetrieb). Ein Rechner hat typischerweise nur einen Prozessor. Das Betriebssystem kann nur ein Programm gleichzeitig als Prozess ausführen. Es weist dem Prozessor eine Programm A zu dieses läuft bis es beendet wird. Anschließend wird das nächste Programm ausgeführt:

Multi tasking: Um interaktive Benutzer zu bedienen ist es geschickter laufende Prozesse zu unterbrechen und andere Prozesse teilweise abzuarbeiten. Aufgrund der hohen Prozessorgeschwindigkeit hat der menschliche Betrachter den Eindruck, die Prozesse laufen gleichzeitig. Alle moderne Betriebsysteme arbeiten nach diesem Prinzip. Das Unterbrechen der Prozesse ist oft problemlos möglich, da sie sich oft selbst blockieren. Sie müssen da si relativ lange auf Daten von Benutzern, Festplatten, dem Netzwerk warten müssen. In diesen vielen Zwangpausen kann das Betriebssyteme andere, lauffähige Prozesse abarbeiten.
Die Prozesse laufen jetzt verschränkt und sie werden in vielen einzelnen Blöcken abgearbeitet. Sie werden quasi-parallel abgearbeitet.

Multi tasking-Multiprozessor: Da alle modernen Prozessoren mehrere Ausführungseinheiten besitzen können die Betriebsysteme Prozesse parallel abarbeiten. Die Prozesse müssen nicht zwangsweise auf dem gleichen Prozessor ausgeführt werden (siehe Beispiel). Die Gesamtausführungszeiten können bei mehren Prozessoren entsprecht verkürzen.

Multithreaded-Multiprozessor: Da Threads leichtgewichtige Prozesse mit einem Programmstack und einem eigenen Programmablauf sind, werden sie bei der Prozessorvergabe wie Prozesse behandelt. Ein Prozess kann jetzt mehrere Prozessoren gleichzeitig verwenden wenn er nur über mehrere Theads verfügt. Anwendungen können jetzt innerhalb eines Prozesses skalieren. Dies bedeutet, sie können (threoretisch) beliebig viele Prozessoren benutzen und damit beliebig viele Aufgaben in einer bestimmten Zeit abarbeiten. Der Durchsatz eines Prozesses, bzw. die Abarbeitungsgeschwindigkeit ist nicht mehr direkt an die Geschwindigkeit eines einzelnen Prozessors gebunden.

Prozesse haben unterschiedliche Zustände, die von den verfügbaren Betriebsmitteln abhängen:
Die Übergänge zwischen den vereinfachten Zuständen eines Prozesses sind im folgenden Diagramm dargestellt:

Threads haben ähnlich wie Prozesse eine Reihe von Zuständen. Sie besitzen jedoch mehr Zustände, da ihre Lebensdauer kürzer als die des Prozesses ist und sie sich miteinandere synchronisieren müssen. Threads haben die folgenden fünf Zustände:
Eine Reihe dieser Zustände kann durch Methodenaufrufe vom Entwickler beeinflusst werden:

Java erlaubt das Erzeugen und Verwalten von Threads mit Hilfe der Systemklasse Thread. Beim Starten einer Javaanwendung bekommt die Methode main() automatisch einen Thread erzeugt und zugewiesen der sie ausführt. Mit Hilfe der Klasse Thread kann man selbst zusätzliche Threads erzeugen und starten.
Man kann einen neuen Thread starten indem man ein Objekt von Thread erzeugt. Hiermit wird parallel im Hintergrund ein Javathread erzeugt. Das Aufrufen der Methode start() startet dann den neuen Thread. Dies geschieht wie im folgenden Beispiel gezeigt:
public static void main(String[] args {
...
Thread t1= new Thread(...);
t1.start();
...
}Im Laufzeitsystem wird hierdurch zuerst ein neuer Thread erzeugt und dann gestartet:

Es verbleibt die Frage welchen Programmcode der neue Thread ausführt.
Der ausgeführte Programmcode steht in einer Methode mit den Namen run() und muss von einer Klasse nach den Vorgaben der Schnittstelle Runnable implementiert werden.
Hierfür muss beim Erzeugen des Thread-objekts eine Referenz auf ein Objekt mitgegeben werden welches diese Schnittstelle implementiert. Geschieht dies nicht wird die Methode run() des Tread-objekts aufgerufen. Hierdurch ergeben sich zwei Möglichkeiten einen eigenen Thread mit einem bestimmten Programm zu starten:
Die erste Möglichkeit besteht im Erweitern der Klasse Thread und im überschreiben der Methode run(). Im unten aufgeführten Beispiel wurde die Klasse myThread aus der Klasse Thread abgeleitet:

Die Klasse myThread verwaltet die Threads in ihrer main() Methode. Das Erzeugen und starten der Threads der Klasse myThread könnte auch aus jeder anderen beliebigen Klasse erfolgen.
package Kurs2.Thread;public class myThread extends Thread {
public void run() {
System.out.println("Hurra ich bin myThread in einem Thread mit der Id: "
+ Thread.currentThread().getId());
}
public static void main(String[] args) {
System.out.println("Start myThread.main() Methode im Thread mit der Id: "
+ Thread.currentThread().getId());
myThread t1 = new myThread();
t1.start();
System.out.println("Ende myThread.main() Methode im Thread mit der Id: "
+ Thread.currentThread().getId());
myThread t2 = new myThread();
// t2 ist zwar ein Threadobjekt und repräsentiert einen Thread
// da das Objekt nicht mit start() aufgerufen läuft es im gleichen
// Thread wie die main() Routine!
t2.run();
}
}
Das Programm erzeugt die folgende Konsolenausgabe:
Start myThread.main() Methode im Thread mit der Id: 1 Hurra ich bin myThread in einem Thread mit der Id: 10 Ende myThread.main() Methode im Thread mit der Id: 1 Hurra ich bin myThread in einem Thread mit der Id: 1
Im Beispiel Programm wird für die Referenz t1 ein neues Threadobjekt erzeugt. Anschliesend wird es durch seine start() Methode in einem eigenen Thread gestartet. Die run() Methode wird nach dem Starten im eigenen Thread ausgeführt.
Das Objekt mit der Referenz t2 ist zwar auch ein Thread. Das es aber direkt mir der run() Methode aufgerufen wird, läuft es i gleichen Thread wie die main() Methode.
Im UML Sequenzdiagramm ergibt sich der folgende Ablauf:

Das Erweitern einer Klasse aus der Klasse Thread ist nicht immer möglich. Man kann einen Thread auch starten indem man ein Thread-objekt erzeugt und ihm die Referenz auf eine Instanz der Schnittstelle Runnable mitgibt. Die Programmierabfolge ist dann:
Die Klasse myRunnable:
package Kurs2.Thread;
public class myRunnable implements Runnable {public void run() {
System.out.println("Hurra ich bin myRunnable in einem Thread mit der Id: "
+ Thread.currentThread().getId());
}
}
Die Klasse ThreadStarter die als Hauptprogramm dient:
package Kurs2.Thread;public class ThreadStarter{
public static void main(String[] args) {
System.out.println("Start ThreadStarter.main() Methode im Thread mit der Id: "
+ Thread.currentThread().getId());
myRunnable r1 = new myRunnable();
myRunnable r2 = new myRunnable();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
System.out.println("Ende ThreadStarter.main() Methode im Thread mit der Id: "
+ Thread.currentThread().getId());
// r2 ist zwar ein Runnableobjekt , da das Objekt aber nicht von einem
// Threadobjekt indirekt aufgerufen wirdläuft es im gleichen
// Thread wie die main() Routine!
r2.run();
}
}
Das Programm erzeugt die gleichen Ausgaben wie das vorherige Programm:
Start ThreadStarter.main() Methode im Thread mit der Id: 1 Hurra ich bin myRunnable in einem Thread mit der Id: 10 Ende ThreadStarter.main() Methode im Thread mit der Id: 1 Hurra ich bin myRunnable in einem Thread mit der Id: 1
Das Aufrufen von r2.run() startet wiederum keinen eigenen Thread. Der Vorteil der Benutzung der Schnittstelle Runnable liegt darin, dass man die Methode run() in jeder beliebigen Klasse implementieren kann.

Da Threads auf die gleichen Objekte auf im Heap zugreifen, können sie so sehr effizient Daten austauschen. Es besteht jedoch das Risiko der Datenkorruption, da man oft mehrere Daten gleichzeitig verändern muss um sie von einem konsistenten Zustand in den nächsten konsistenten Zustand zu überführen.
Die Sitzplatzreservierung in einem Flugzeug ist hierfür ein typisches Beispiel:
Mehrere Reisebüros prüfen ein Flugzeug auf die Verfügbarkeit von 10 Plätzen für eine Reisegruppe. Ergibt das Lesen der Belegungsvariable 20 freie Plätze, so fährt Reisebüro 1 fort und liest weitere Daten um die Buchung vorzubereiten. Verzögert sich die endgültige Buchung so kann es vorkommen, dass ein zweites Reisebüro die Verfügbarkeit von 15 Plätzen abfragt und die 15 Plätze bucht. Das zweite Reisebüro erhöht den Belegungszähler also um 15 Plätze.
Kommt das erste Reisebüro nun endlich mit seiner Buchung vorran und erhöht die ursprünglich ausgelesene Variable um 10 ergibt sich ein inkonsistenter Zustand.
Man muss also in Systemen mit parallelen Zugriff auf Daten die Möglichkeit schaffen nur einen Thread über eine gewisse Zeit auf einem Datensatz (Objekt) arbeiten zu lassen um wieder einen konsistenten Zustand herzuführen.

Darf nur ein Thread gleichzeitig auf einer Variablen arbeiten, so nennt man diese eine kritische Variable. Die Zeit die ein Thread mit der Bearbeitung einer solchen Varriablen verbringt nennt man den kritischen Abschnitt oder auch den kritischen Pfad.
Das oben geschilderte Problem beim gleichzeitigen Zugriff nennt man auch "Reader/Writer" Problem, da das Lesen und Schreiben auf dem Datum atomar erfolgen muss. Da in in nebenläufigen Systemen diese Datenkorruption ausschlieslich von der Geschwindigkeit und dem zufälligen paralleln Zugriff abhängt nennt man ein solches Problem auch eine "Race Condition". Die Datenkorruption tritt zufällig und abhängig von der Ausführungsgeschwindigkeit auf.
Der ++ Operator ist Java ist nicht atomar. Dies bedeutet, dass zwei Threads einen bestimmten Wert auslesen können und der erste Thread schreibt den um 1 erhöhten Wert zurück während eventuell der zweite Thread noch etwas Zeit benötigt. Schreibt der zweite Thread dann den gleichen inkrementierten Wert zurück wurde die Zahl nur einmal inkrementiert. Es liegt eine Datenkorruption vor.
Im Programm ParaIncrement wird eine gemeinsame Variable zaehler von zwei Threads gleichzeitig inkrementiert. Der Wert der Vriablen sollte immer doppelt so groß wie die Anzahl der Durchläufe (Konstante MAX) eines einzelnen Threads sein.Die Korruption im Programm ParaIncrement findet relativ selten selten statt. Man muss die Konstante K für die Anzahl der Durchläufe eventuell abhängig vom Rechner und der Java virtuellen Maschine anpassen:
package Kurs2.Thread;public class ParaIncrement extends Thread {
public static int zaehler=0;
public static final int MAX= Integer.MAX_VALUE/10;
public static void increment() {
zaehler++;
}
/**
* Starten des Threads
*/
public void run() {
for (int i=0; i < MAX; i++) {
increment();
}
}
public static void main(String[] args) {
ParaIncrement thread1 = new ParaIncrement();
ParaIncrement thread2 = new ParaIncrement();
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
}
if ((2* ParaIncrement.MAX) == ParaIncrement.zaehler)
System.out.println("Korrekte Ausführung: " +
+ ParaIncrement.zaehler);
else
System.out.println("Fehler! Soll: " + (2* ParaIncrement.MAX) +
"; Ist: " +ParaIncrement.zaehler);
}
}
Zur Vermeidung der oben genannten Korruption ist es wichtig sicher zustellen, dass nur ein Thread gleichzeitig Zugriff auf diese Daten hat.
| Kritischer Abschnitt/ Kritischer Pfad |
|---|
| Ein kritischer Abschnitt ist eine Folge von Befehlen, die ein Thread nacheinander vollständig abarbeiten muss, auch wenn er vorübergehend die CPU and einen anderen Thread abgibt. Kein anderer Thread darf einen kritischen Abschnitt betreten, der auf die gleichen Variablen zugreift, solange der erstgenannte Thread mit der Abarbeitung der Befehlsfolge noch nicht fertig ist. (siehe: Goll, Seite 744) |
Um die oben gezeigten Möglichkeiten von Korruptionen zu vermeiden verfügen Javaobjekte über Sperren die Monitore genannt werden. Jedes Javaobjekt besitzt einen Monitor der gesetzt ist oder nicht gesetzt ist.
Es ist wichtig zu verstehen, dass in Java immer die individuellen Objekte mit einem Monitor geschützt sind. Sind zum Beispiel die Sitzplätze eines Flugzeuges durch Java-Objekte implementiert, so kann man mit der gleichen synchronisierten Methode auf untrschiedlichen Objekte parallel arbeiten.
Am Beispiel der Klasse Sitzplatz kann man sehen wie man den Monitor für einen bestimmten Sitzplatz setzen kann:
package Kurs2.Thread;public class Sitzplatz {
private boolean frei = true;
private String reisender;
boolean istFrei() {return frei;}
/**
* Buche einen Sitzplatz für einen Kunden falls er frei ist
* @param kunde Name des Reisenden
* @return erfolg der Buchung
*/
synchronized boolean belegeWennFrei(String kunde) {
boolean erfolg = frei; // Kein Erfolg wenn nicht frei
if (frei) {
reisender = kunde;
frei = false;
}
return erfolg;
}}
Die Methode belegeWennFrei() kann jetzt nur noch von einem Thread auf einem Objekt gleichzeitig aufgerufen werden. Die Methode istFrei() ist nicht synchronisiert und in einer parallelen Umgebung nicht sehr relevant. Man kann sich nicht darauf verlassen, dass bei der nächsten Operation der freie Zustand noch gilt.

| Monitore und Schutz von Daten |
|---|
|
Monitore schützen nur die synchronsierten Methoden eines Objekts. Dies bedeutet
|
Das Schlüsselwort synchronized kann auch verwendet werden um einen Monitor für die Klasse zu setzen. Dieser Monitor hat aber keinen Einfluss auf den Zugriff auf die Objekte einer Klasse! Er schützt nur statische Methoden der Klasse.
Beispiel: Man kann das Korruptionsproblem in der Klasse ParaIncrement beheben in dem man die statische Methode increment() synchronsiert:
public static synchronized void increment() {
zaehler++;
}
Die Variable zaehler ist in diesem Beispiel eine statische Variable. Sie gehört nicht zu einem der beiden erzeugten Objekten.
Man muss nicht notwendigerweise eine gesamt Methode synchroniseren. Java bietet auch die Möglichkeit einzelne Blöcke zu synchronisieren. Das Synchronsieren eines Blocks erfolgt ebenfalls mit Hilfe des Schlüsselworts synchronized. Hier muss man jedoch das Objekt angeben für welches man einen Monitor erwerben will.
Man kann die Sitzplatzreservierung auch mit Hilfe eines synchronisierten Blocks implementieren:
boolean belegeWennFrei(String kunde) {
boolean erfolg;
synchronized (this) {
erfolg = frei; // Kein Erfolg wenn nicht frei
if (frei) {
reisender = kunde;
frei = false;
}
}
return erfolg;
}
In dieser Implementierung kann die Methode belegeWennFrei() parallel aufgerufen werden. Beim Schlüsselwort synchronized muss jedoch für das aktuelle Objekt der Monitor erworben werden bevor der Thread fortfahren kann.
Ziel der Aufgabe ist es drei Threads zu programmieren die auf das Beenden des anderen Warten und dann eine Zeit schlafen:

Die geforderte Aufgabe soll in einer Klasse implementiert werden

Die Methode Thread.join() erlaubt auf das Beenden eines anderen Threads zu warten. Man muss auf eine InterruptedException vorbereitet sein, da man aufgeweckt werden kann:
Thread aThread;
...
try {
aThread.join();
} catch (InterruptedException e) {}
Die Methode Thread.sleep() ist eine statische Methode. Man muss seinen eigenen Thread nicht kennen um ihn ruhen zulassen! Auch diese Methode kann eine InterruptedException werfen und muss mit einer Ausnahmebehandlung versehen werden:
try {
Thread.sleep(schlafen);
} catch (InterruptedException e) {}
package Kurs2.Thread;
public class JoinAndSleep extends Thread{
Thread joinIt;
int schlafen;
public JoinAndSleep(int sleeptime, Thread toJoin) {
joinIt = toJoin;
schlafen = sleeptime;
System.out.println("Thread: " + this + " erzeugt");
}
public void run() {
System.out.println("Thread: " +Thread.currentThread() + " gestartet");
try {
if (joinIt!=null) {
joinIt.join();
System.out.println("Thread: " +Thread.currentThread()
+ " join auf " + joinIt + " fertig");
}
} catch (InterruptedException e) {}
System.out.println("Thread: " +Thread.currentThread()
+ " schlaeft jetzt fuer " + schlafen + "ms");
try {
Thread.sleep(schlafen);
} catch (InterruptedException e) {}
System.out.println("Thread: " +Thread.currentThread() + " endet");
}
public static void main (String[] args) {
JoinAndSleep s3= new JoinAndSleep(2003, null);
JoinAndSleep s2= new JoinAndSleep(2002, s3);
JoinAndSleep s1= new JoinAndSleep(2001, s2);
s1.start();
s2.start();
s3.start();
}
}
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:
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.
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.
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();
}
}
}
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();
}
}
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);
}
}
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 Pixelpublic 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);
}
}
|
(Lizenz) |
Am Ende dieses Blocks können Sie:
|
Sie sind in der Lage die folgenden Fragen zu beantworten: