2.1 Grundlegende Eigenschaften
Java wurde von der Firma Sun entwickelt und erstmals am 23. Mai 1995 als neue, objekt-orientierte und plattformunabhängige Programmiersprache vorgestellt. Durch die Portabilität ist Java ideal für das Internet. Kleine Java Programme, die sogenannten Applets, können durch ein eigens dafür definiertes "Tag" in HTML-Seiten eingebunden werden und dadurch die statischen HTML- Seiten mit Leben erfüllen. Heutzutage werden immer mehr Internetanwendungen mit Hilfe von Applets umgesetzt.
Einige Eigenschaften von Java sind:
Portabel | Java ist portabel. Dies wird dadurch erreicht, daß eine virtuelle Maschine, die für alle heutigen Betriebssysteme erhältlich ist, den kompilierten Bytecode der Java Anwendungen ausführt. |
Objektorientiert | Java ist vollständig objektorientiert aufgebaut. Es ähnelt in weiten Zügen der Programmiersprache C++. |
Multithreaded | siehe Kapitel 2.2 |
Verteilt | Die Standardbibliothek von Java bietet bereits Klassen, mit denen verteilte Anwendungen auf Basis von TCP/IP einfach realisiert werden können. |
Robust | Es gibt
mehrere Eigenschaften von Java, die die Robustheit der
Anwendungen erhöht. Hierfür wurden auf einige Elemente
verzichtet, die bei der Programmiersprache C++ oft zu
Fehler bzw. Instabilität der Programme führen.
|
Sicher | Java wurde ursprünglich für Online-Systeme im Internet entwickelt. Aus diesem Grund wurde besonderen Wert auf die Sicherheit gelegt. Diese Sicherheit ist besonders wichtig, wenn Applets von einem beliebigen Server auf den eigenen Rechner geladen und ausgeführt werden. Da Java-Programme keine echten Binärprogramme für ein bestimmtes Betriebssystem sind, sondern Bytecode für eine virtuelle Maschine, kann in der Umsetzung des Interpreters für die virtuelle Maschine einiges an Sicherheit erreicht werden. |
2.2 Prozesse / Threads in Java
Java ist eine Programmiersprache, die Multithreading unterstützt. Dies bedeutet, daß ein Prozess (Anwendung) in verschiedene Teile aufgeteilt werden kann. Diese Teilaufgaben können so von verschiedenen Threads "parallel" abgearbeitet werden. Inhaltlich sind einzelne Threads eine in sich sequentielle Abfolge von Anweisungen. Als Ganzes gesehen laufen sie jedoch "parallel" zu andern Threads.
Das Programmieren von Threads ist z.B. sinnvoll bei der Arbeit mit Applets, da es so möglich ist mehrere Aufgaben unabhängig voneinander ablaufen zu lassen. Ein Thread kann sich z.B. um die Aktualisierung der Bildschirmausgabe kümmern während ein anderer Thread die Kommunikation mit dem Server übernimmt. Zu diesem Zweck bietet Java Primitiven an um Threads zu steuern und zu synchronisieren.
Abb 2.1: Zustände eines Threads [MiSi1999]
Java Threads können sich wie Threads bzw. Prozesse vom Betriebssystem in verschiedene Zustände befinden.
Ein Thread besitzt beim Aufruf dieser beiden Methoden keine Möglichkeit eventuelle Synchronisationsmechanismen zurückzusetzen.
In Java wird die Priorität von Threads in 10 Stufen eingeteilt. Die niedrigste Priorität ist 1 und die höchste Priorität ist 10. Wird ein Thread erzeugt besitzt er die Priorität 5 (Thread.NORM_PRIORITY). Diese Priorität kann mit der Methode setPriority() geändert werden. Die Methode getPriority() liefert die aktuelle Priorität des Thread.
Das Scheduling erfolgt in Java hierarchisch was bedeutet, daß ein Thread erst dann ausgeführt wird, wenn alle Threads mit einer höheren Priorität abgearbeitet sind oder sich nicht im Zustand rechenwillig befinden.
Besitzen zwei Threads die selbe Priorität werden keine Angaben über das Scheduling gemacht. In diesem Fall besitzt das Scheduling des Betriebssystem Einfluß auf die Abarbeitung der einzelnen Threads. Es ist daher sinnvoll eine Applikation, die Multithreading nutzt und auf verschiedenen Plattformen laufen soll, auf den einzelnen Plattformen zu testen.
Ein Dämon-Thread ist ein Thread der entweder vom System oder vom Entwickler gestartet wird um im Hintergrund bestimmte Aufgaben auszuführen. Möchte man ein Dämon-Thread erzeugen, so muß man lediglich nach der Erzeugung eines Threads aber vor seinem Start mit dem Aufruf setDaemon(true) das Dämon-Flag des Threads auf true setzen. Mit dem Aufruf isDaemon() kann festgestellt werden ob ein Thread ein Dämon Thread ist oder nicht.
Ein Java-Programm wird dann beendet, wenn alle seine "normalen" Threads (auch User-Threads genannt) beendet sind. Dämon-Threads werden hierbei nicht berücksichtigt. Sobald alle User-Threads beendet sind werden auch die Dämon-Threads beendet.
Bei der Konzeption eigener Dämon-Threads ist zu berücksichtigen, daß das Programmende für Dämon-Threads asynchron, z.B. mitten in der run() Methode eintreten kann.
Threads werden in Java einer Thread Gruppe zugeordnet. In einer sogenannten ThreadGroup können Threads zusammengefasst werden, die von ihrer Zugehörigkeit oder ihrer Aufgabe nach eine Einheit bilden. Wird ein Thread nicht explizit einer Gruppe zugeordnet so wird er der Gruppe zugeordnet, welcher der erzeugende Thread angehört. Ohne besondere Maßnahmen ist dies eine voreingestellte Gruppe, die automatisch von der Virtual Maschine angelegt wird. Bei Applikationen heißt diese Gruppe "main", bei Applets legt der Browser für jedes Applet eine eigene Gruppe an, die den Name der Applet-Klasse trägt. Eine ThreadGroup kann sowohl Threads als auch eine weitere ThreadGroup enthalten.
Abb. 2.2: Hierarchische Struktur der Thread-Groups [MiSi1999]
Wie in Abb. 2.2 zu sehen ist entsteht durch diese Anordnung der Thread Gruppen eine baumartige Struktur. Diese hierarchische Struktur kann in der Klasse SecurityManager verwendet werden um Zugriffsrechte zuzuteilen. Damit soll verhindert werden das ein Threads die Priorität der anderen Threads soweit herabstuft das nur noch er die CPU zugeteilt bekommt. Diese Sicherung gilt allerdings nur für Applets und nicht für "normale" Anwendungen. Der Browser legt für jedes Applet eine eigene Thread Gruppe an, die unter der Hauptgruppe steht, in der die System-Threads laufen. Der SecurityManager erlaubt einem Thread nur Threads seiner Gruppe sowie auf Threads aller Untergruppen zuzugreifen. Durch diese Zugriffssteuerung wird verhindert, daß ein Applet den Ablauf von System-Threads beeinträchtigt oder Threads von anderen Applets manipulieren kann.
Folgende Methoden sind von dieser Zugriffssteuerung betroffen:
2.7 Erzeugung und Steuerung von Threads
Vom objektorientierten Standpunkt ist ein Thread in Java auch ein Objekt. Es gibt in Java zwei verschiedene Möglichkeiten den Code einer Klasse als Thread ausführen zu lassen.
Klasse von Thread ableiten
Man leitet eine Klasse von der Klasse Thread ab und
überschreibt die run() Methode. Um einen Thread zu
erzeugen wird der Konstruktor der abgeleiteten Klasse aufgerufen.
Gestartet wird dieser Thread anschließend mit dem Aufruf von start().
Beispiel:
class MeinThread extends Thread { // abgeleitete Klasse MeinThread (){} ; // Konstruktor public void run { ** Hier steht der Programmcode der als Thread ausgeführt werden soll** } } class EinThread { // Main-Methode MeinThread thread1= new MeinThread(); // thread1 wird erzeugt thread1.start(); // thread1 wird gestartet }
Das
Interface Runnable
Ist eine Klasse bereits von einer andern abgeleitet implementiert
man das Interface Runnable damit der Programmcode, der in der run() Methode steht, als Thread
ausgeführt wird. Das Erzeugen eines Threads erfolgt hierbei in
zwei Stufen. Als erstes wird ein Objekt der Klasse erzeugt.
Danach wird dieses Objekt dem Konstruktor Thread(Runnable) übergeben.
Beispiel:
// Klasse mit Interface Runnable class MeinThread implements Runnable { MeinThread() {}; // Konstruktor public void run { ** Programmcode der als Thread ausgeführt werden soll** } } class EinThread { // Main-Methode Runnable rthread = new MeinThread(); // Runnable Objekt und Thread thread1 = new Thread(rthread); // thread1 wird erzeugt thread1.start() // thread1 wird gestartet }
Der Zusammenhang zwischen Runnable-Objekt und Thread-Objekt ist ähnlich wie zwischen einem Auto und seinem Lenker.
Um einen Thread auf einfache Art zu beenden konnte man bis zur Version 1.1 einfach die Methode Thread.stop() aufrufen. In der Version 1.2 wurde diese Methode allerdings verworfen, da sie unter bestimmten Umständen problematisch ist. Die Methode Thread.stop() beendet den Thread ohne ihm die Möglichkeit zu geben Monitore oder Ergebnisse, die er besitzt, zurückzugeben. Bei den Beispielprogrammen wurde die Methode Thread.stop() angewendet wenn die gesamte Anwendung, mit allen Threads, beendet werden soll, da es in dieser Situation keinen Unterschied macht ob ein Thread eventuell einen Monitor besitzt oder nicht.
Besteht der Rumpf eines Threads aus einer Schleife und möchte man diesen Thread so beenden, daß er die Möglichkeit besitzt eventuelle Monitore oder Ergebnisse zurückzugeben sollte statt dessen folgendes Muster anwendet werden:
Beispiel :
class MeinThread extends Thread { MeinThread () {}; Thread t ; // wird automatisch mit null initialisiert public void start() { if (t == null ) { // Wenn kein Thread angelegt ist, t = new Thread(this); // anlegen und starten des Threads t.start() ; } public void stop() { // setzen der Referenz auf null t = null ; } public void run() { Thread me = Thread.currentThread(); // Referenz des aktuellen // Threads zuordnen while ( me == t ) { // Referenzen vergleichen ** Programmcode der ausgeführt werden soll ** } } }
Java bietet für die Arbeit mit Threads eine große Anzahl von Konstruktoren und Methoden. Konstruktoren und Methoden, die in dieser Diplomarbeit verwendet bzw. angesprochen werden, werden hier eingehender erläutert [MiSi1999].
Das Paket java.lang.Thread
Datenelemente:
public final static int MIN_PRIORITY = 1
Die niedrigste Priorität, die
ein Thread haben kann.
public final static int NORM_PRIORITY = 5
Die zunächst für alle
Threads voreingestellte Priorität.
public final static int MAX_PRIORITY = 10
Die höchste Priorität, die
ein Thread haben kann.
Konstruktoren:
public Thread()
Erzeugt einen neuen Thread.
public Thread(Runnable target)
Erzeugt einen neuen Thread für die Runnable-Implementierung target.
Methoden:
currentThread
public static native Thread currentThread()
Liefert einen Verweis auf den momentan aktiven Thread.
getName
public final String getName()
Liefert den Namen des Threads.
getPriority
public final int getPriority()
Liefert die Priorität des Threads.
isAlive
public final native boolean isAlive()
Liefert true, wenn der Thread lauffähig ist. Dies ist der Fall, wenn seine start() Methode, aber noch nicht seine stop() Methode aufgerufen wurde. Der Rückgabewert false signalisiert, daß der Thread nicht lauffähig ist. Das ist der Fall, wenn der Thread neu erzeugt wurde, ohne daß bisher seine start() Methode aufgerufen wurde, oder wenn die Ausführung seiner run() Methode ordnungsgemäß beendet oder seine stop() Methode aufgerufen wurde.
isDaemon
public final boolean isDaemon()
Liefert true, wenn der Thread als Daemon läuft, sonst false.
join
public final void join()
Wartet auf die Beendigung des Threads.
run
public void run()
Diese Methode enthält den eigentlichen Anweisungsteil, der nach der Aktivierung ausgeführt wird. Hierzu muß sie überschrieben werden, wenn beim Aufruf des Konstruktors kein Runnable-Objekt übergeben wurde. Falls dem Konstruktor ein Runnable-Objekt übergeben wurde, wird dessen run() Methode ausgeführt.
setDaemon
public final void setDaemon(boolean on)
Läßt den Thread als Daemon oder User-Thread ausführen, wenn on true bzw. false ist.
setPriority
public final void setPriority(int newPriority)
Setzt die Priorität des Threads auf newPriority. newPriority muß im Bereich von MIN_PRIORITY und MAX_PRIORITY liegen.
sleep
public static native void sleep(long millis)
Bewirkt, daß die Ausführung des Threads millis Millisekunden ausgesetzt wird.
start
public synchronized native void start()
Startet den Thread durch Aufruf der run()Methode.
stop
public final void stop()
Hält den Thread an. Diese Methode wurde in Version 1.2 verworfen, da sie Inkonsistenzen verursachen kann, wenn sie bei einem Thread aufgerufen wird, der gerade eine Zugangsmethode eines Monitors ausführt.
yield
public static native void yield()
Mit dem Aufruf dieser Methode unterbricht der Thread seine Ausführung, um anderen Threads mit der gleichen Priorität die Möglichkeit zur Ausführung zu geben. yield() bewirkt nichts, wenn momentan keine Threads verfügbar sind, die dieselbe Priorität haben und rechenwillig sind.