Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

Java Programmiersprachenkonzepte Kai Meyer & Torsten Witte

Ähnliche Präsentationen


Präsentation zum Thema: "Java Programmiersprachenkonzepte Kai Meyer & Torsten Witte"—  Präsentation transkript:

1 Java Programmiersprachenkonzepte Kai Meyer & Torsten Witte
Universität HH, SS 2004 Kai Meyer & Torsten Witte

2 Inhalt Ursprünge & Anwendungsgebiete Funktionsweise von Java
Datentypen, Operatoren, Kontrollfluss Vererbung, Polymorphie Threads RMI Jini JavaSpace

3 Ursprünge von Java 23. Mai 1995 Vorstellung von Java durch Sun
Der Name stammt von einer in Amerika üblichen Bezeichnung für Kaffee Einfache und kompakte, Objekt-orientierte und plattformunabhängige Programmiersprache für Unix-Workstations, PCs und Apple, sowie Micro-Computer in Haushalts- oder Industriegeräten Java ist für alle Computersysteme (im Allgemeinen kostenlos) verfügbar Für kommerzielle Anwendungen muss man für die Compiler-Software eine kostenpflichtige Lizenz erwerben

4 Anwendungsgebiete Ursprünglich:
Steuerung von Haushaltsgeräten und PDAs Heute: Datenbanken Betriebssysteme Internet Web-Browser Unterhaltungselektronik Handys Haushaltsgeräte (Toaster, Mikrowelle, Kaffeemaschine, Waschmaschinen, Videorekorder) Autos Verkehrsampeln Kreditkarten TV-Settop-Boxes für "intelligente" Fernsehapparate wissenschaftliche Programmierung usw. Ursprünglich wurde Java zur Steuerung von Haushaltsgeräten und PDA. s konzipiert. Die heutigen Anwendungsgebiete sind Datenbanken, Betriebssysteme, Internet, Web-Browser, Unterhaltungselektronik, Haushaltsgeräte (Toaster, Mikrowelle, Kaffeemaschine, in Waschmaschinen, Videorekorder), Autos, Verkehrsampeln, Kreditkarten, in TV-Settop-Boxes für "intelligente" Fernsehapparate und die wissenschaftliche Programmierung.

5 Bytecode und JVM Abstrakte Definition der Maschine, deren Verhalten von dem jeweiligen ausführenden Rechner simuliert wird Java-Compiler erzeugt plattformunabhängigen Bytecode Der Bytecode kann auf jedem anderen Computersystem durch die Java Virtual Machine (JVM) ausgeführt werden Sandkastenprinzip

6 Elementare Typen Ganzzahlige Typen: Dezimaltypen: Zeichentyp:
byte 8 Bits (byte) bis 127 short 16 Bits (short) bis int 32 Bits bis long 64 Bits 42L bis Dezimaltypen: float 32 Bits 42.0F -3.4*1038 bis 3.4*1038 double 64 Bits *10308 bis 1.8*10308 Zeichentyp: char 16 Bits ‘A‘ Zeichen und Symbole Logischer Typ: boolean true true, false Tabellenaufbau: Typname Speicher Beispiel für ein Literal Wertebereich Angaben ohne Gewähr!

7 Operatoren Zuweisung: Vergleichsoperatoren für Zahlen und Zeichen:
== ist gleich equals ist gleich (bei Objekten) != ist ungleich < ist kleiner als > ist größer als <= ist kleiner oder gleich >= ist größer oder gleich Logische Operatoren: && und || oder ! nicht Call by Value / Reference

8 Kontrollfluss (1) Bedingte Verzweigung: if (Bedingung)
{Anweisung1; Anweisung2; ...} else switch (variable) { case wert_1: Anweisung1; break; case wert_2: Anweisung2; break; ... default: Defaultanweisung; break; }

9 Kontrollfluss (2) Schleifen:
for (Initialisierung; Ausdruck; Aktualisierung) {Anweisung1; Anweisung2;...} Beispiel: for (int i=0; i<10; i++) { System.out.println(i); } while (Bedingung) // solange noch Bedingung do // solange bis Bedingung while (Bedingung) Bei einem break-Befehl in einer Schleife springt das Programm aus der Schleife heraus (zu dem ersten Befehl, der auf die Schleife folgt)

10 Vererbung (Unter-)Klassen können Operationen von anderen (Ober-)Klassen implementieren (falls dies nicht in der Oberklasse geschehen ist), redefinieren (verdecken, überschreiben), erben, oder neue Operationen hinzufügen In Java ist nur Einfachvererbung zwischen Klassen möglich: Oberklasse: Unterklassen:

11 Polymorphie Objekte einer Unterklasse können an Bezeichner gebunden werden, die mit dem Typ der Oberklasse deklariert sind Dies kann durch Zuweisung oder durch Parameterübergabe geschehen Beispiel: public class Oberklasse {...} public class Unterklasse extends Oberklasse {...} public class KlasseX { public void setY(Oberklasse y) {...} } Unterklasse _unterklassenbezeichner = new Unterklasse(); Oberklasse _oberklassenbezeichner; _oberklassenbezeichner = _unterklassenbezeichner; // Zuweisung KlasseX _x = new KlasseX(); _x.setY(_unterklassenbezeichner); // Parameterübergabe Anders formuliert: Die Zuweisung von Unterklassen-Objekten auf Bezeichner einer Oberklasse ist zulässig. Durch Zuweisung oder durch Parameterübergabe kann ein Objekt einer Unterklasse an einen Bezeichner einer Oberklasse gebunden werden. Polymorphie = Vielgestaltigkeit

12 Threads Java unterstützt die nebenläufige Programmierung direkt
Das Konzept beruht auf so genannten Threads (zu Deutsch »Faden« oder »Ausführungsstrang«), das sind parallel ablaufende Aktivitäten, die sehr schnell in der Umschaltung sind Threads können vom Scheduler sehr viel schneller umgeschaltet werden als Prozesse, sodass wenig Laufzeitverlust entsteht Threads werden entweder direkt von dem Betriebssystem unterstützt oder von der virtuellen Maschine simuliert Die Integration in die Sprache macht das Entwerfen nebenläufiger Anwendungen in Java einfacher Java realisiert gegenseitigen Ausschluss von Methoden verschiedener Threads, Methoden gleicher Threads schließen sich nicht aus Die Nebenläufigkeit der Programme wird durch das Betriebssystem gewährleistet, welches auf Einprozessormaschinen die Prozesse alle paar Millisekunden umschaltet. Daher ist das Programm nicht wirklich parallel, sondern das Betriebssystem gaukelt uns dies durch verzahnte Bearbeitung der Prozesse vor. Java unterstützt die nebenläufige Programmierung direkt Das Konzept beruht auf so genannten Threads (zu Deutsch »Faden« oder »Ausführungsstrang«), das sind parallel ablaufende Aktivitäten, die sehr schnell in der Umschaltung sind. Unterstützt das Betriebssystem des Rechners, auf dem die Java virtuelle Maschine läuft, Threads direkt, so nutzt die Laufzeitumgebung diese Fähigkeit in der Regel. In diesem Fall haben wir es mit nativen Threads zu tun. Falls das Betriebssystem jedoch keine Threads unterstützt, wird die Parallelität von der virtuellen Maschine simuliert. Der Java-Interpreter regelt dann den Ablauf, die Synchronisation und die verzahnte Ausführung. Durch die verzahnte Ausführung kommt es allerdings zu Problemen, die Datenbankfreunde von Transaktionen kennen. Es besteht die Gefahr konkurrierender Zugriffe auf gemeinsam genutzte Ressourcen und damit steigt die Gefahr für Verklemmungen (engl. Dead-Locks). Um dies zu vermeiden, kann der Programmierer durch synchronisierte Programmblöcke gegenseitigen Ausschluss sicherstellen. Java realisiert gegenseitigen Ausschluss von Methoden verschiedener Threads. Methoden gleicher Threads schließen sich nicht aus. Mit synchronized werden kritische Abschnitte realisiert. Nett wäre eine Information am Rande, wo in den Standard-Bibliotheken Threads zugelassen oder ausgeschlossen werden. Die AWT/Swing-Klassen verlassen sich z.B. darauf, dass es maximal einen Ereignisbearbeitungs-thread gibt. Wie reagieren beispielsweise die oft verwendeten java.util.Collection-Klassen auf nebenläufige Zugriffe?

13 Implementation von Threads Variante 1
Threads über die Schnittestelle Runnable implementieren: Z.B. zwei Threads, wobei einer zwanzigmal das aktuelle Datum und die Uhrzeit ausgibt und der andere einfach eine Zahl. class DateThread implements Runnable { public void run() { for ( int i=0; i<20; i++ ) System.out.println( new Date() ); } class CounterThread implements Runnable { for ( int i=0; i<20; i++ ) { System.out.println( i ); } Eine Klasse, deren Exemplare Programmcode parallel ausführen sollen, muss die Schnittstelle Runnable implementieren, die eine run()-Methode vorschreibt.

14 Implementation von Threads Variante 1
Direkter Aufruf von run()  Sequenzielle Ausführung des Codes Parallele Ausführung: 1.) Erzeugung eines Thread-Objekts 2.) Aufruf von start() public class FirstThread { public static void main( String args[] ) { Thread t1 = new Thread( new DateThread() ); t1.start(); Thread t2 = new Thread( new CounterThread() ); t2.start(); } } Nachdem start() für den Thread eine Ablaufumgebung geschaffen hat, ruft es dann selbstständig die Methode run() genau einmal auf. Läuft der Thread schon, so wirft die start()-Methode eine IllegalThreadStateException Erst der Aufruf der start()-Methode lässt den Thread ablaufen. Etwas eleganter ist der Weg, dass das Objekt seinen eigenen Thread verwaltet, der im Konstruktor gestartet wird. Dann muss nur dort ein Thread-Objekt für die Runnable-Umgebung angelegt und die start()-Methode ausgeführt werden. Nun reicht es nicht aus, einfach die run()-Methode einer Klasse direkt aufzurufen. Würden wir dies tun, dann wäre nichts nebenläufig, sondern wir würden einfach eine Methode sequenziell ausführen. Damit der Programmcode nebenläufig zur Applikation läuft, müssen wir ein Thread-Objekt erzeugen und dann den Thread explizit starten. Dazu übergeben wir dem Konstruktor der Klasse Thread eine Referenz auf das Runnable-Objekt und rufen start() auf. Nachdem start() für den Thread eine Ablaufumgebung geschaffen hat, ruft es dann selbstständig die Methode run() genau einmal auf. Läuft der Thread schon, so wirft die start()-Methode eine IllegalThreadStateException.

15 Implementation von Threads Variante 2
Die Klasse Thread erweitern: Beispiel: class DateThreadExtends extends Thread { public void run() { for ( int i=0; i<20; i++ ) System.out.println( new Date() ); } Der Thread startet wieder beim Aufruf von start(): Thread t = new DateThreadExtends(); t.start(); Oder auch ohne Zwischenspeicherung der Objektreferenz: new DateThreadExtends().start(); Da die Klasse Thread selbst die Schnittstelle Runnable implementiert und die run()-Methode mit leerem Programmcode bereitstellt, können wir auch Thread erweitern. Dann müssen wir kein Runnable-Exemplar mehr in den Konstruktor stecken, denn wenn unsere Klasse Unterklasse von Thread ist, reicht ein Aufruf der geerbten Methode start().

16 Gegenüberstellung der beiden Implementationen
Vorteil Nachteil Ableiten von Thread (extends Thread) Programmcode in run() kann die Methoden der Klasse Thread nutzen Da es in Java keine Mehrfachvererbung gibt, kann die Klasse nur Thread erweitern Implementieren von Runnable (implements Runnable) Die Klasse kann von einer anderen, problemspezifischen Klasse erben Kann sich nur mit Umwegen selbst starten; allgemein: Thread-Methoden können nur über Umwege genutzt werden Eine unangenehme Begleiterscheinung des Erweiterns von Thread ist, dass die Möglichkeit der Vererbung nicht genutzt werden kann, wenn die neue Thread-Unterklasse noch weitere Funktionalität erben soll – denn Mehrfachvererbung ist in Java ja nicht möglich. Dies ist häufig das Dilemma, wenn ein Applet nebenläufige Programmstücke besitzt. Hier können allerdings innere Klassen das Problem lösen. Die Tabelle fasst die Vor- und Nachteile der beiden Alternativen noch einmal zusammen Eine Erweiterung der Klasse Thread hat den Vorteil, dass alle geerbten Methoden sofort genutzt werden können. Wenn wir Runnable implementieren, dann genießen wir den Vorteil nicht. Dazu bietet Thread die Klassenmethode currentThread() an, um die Objektreferenz für das Thread-Exemplar zu holen, das gerade diese Methode ausführt.

17 Threads beenden Allgemein ist ein Thread beendet, wenn eine der folgenden Bedingungen zutrifft: Die run()-Methode wurde ohne Fehler beendet. Wenn wir eine Endlosschleife programmieren, würde diese dann potenziell einen nie endenden Thread bilden. In der run()-Methode tritt eine Exception auf, die die Methode beendet. Der Thread wurde von außen abgebrochen. Dazu dient die prinzipbedingt problematische Methode stop(), von deren Verwendung abgeraten wird und die auch veraltet ist. Besser, z.B. wenn Thread eine Endlosschleife enthält: Mit der Methode interrupt() von außen in einem Thread-Objekt ein internes Flag setzen, welches dann in der Endlosschleife (in der run()-Methode) durch isInterrupted() periodisch abgefragt wird und falls diese true liefert mit break die Schleife verlassen und so den Thread ohne Fehler terminieren lassen Leider verbraucht die dauernde Fragerei auch Laufzeit. Natürlich haben die Java-Entwickler an eine Lösung schon gedacht und drei Methoden implementiert, mit denen sich eine Unterbrechung ankündigen und abfragen lässt. Mit der Methode interrupt() wird in einem Thread-Objekt von außen ein internes Flag gesetzt, welches dann in der run()-Methode durch isInterrupted() periodisch abgefragt werden kann. Unerwartetes: Durch die Unterbrechung wird das interne Flag zurückgesetzt, sodass isInterrupted() meint, die Unterbrechung habe gar nicht stattgefunden. Daher muss interrupt() erneut aufgerufen werden, da das Abbruch-Flag neu gesetzt werden muss, und isInterrupted() das Ende bestimmen kann. Wenn wir mit der isInterrupted()-Methode arbeiten, dann müssen wir beachten, dass auch die Methoden join() und wait() durch die InterruptedException das Flag löschen

18 Zustände von Threads feststellbare Zustände: - nicht erzeugt
- laufend (vom Scheduler berücksichtigt) - nicht laufend (vom Scheduler nicht berücksichtigt) - wartend - beendet Zusatzinfo: Ein Server reagiert oft in einer Endlosschleife auf eingehende Aufträge und führt die gewünschte Aufgabe aus. In unseren bisherigen Programmen ist aufgefallen, dass ein gestarteter Thread, sofern er eine Endlosschleife wie ein Server enthält, nie beendet wird. Wenn also run(), wie in den vorangehenden Beispielen, nie abbricht (Informatiker sprechen hier von terminiert nie), so läuft der Thread immer weiter, auch wenn die Haupt-Applikation beendet ist. Dies ist nicht immer beabsichtigt, denn Serverfunktionalität ist nicht mehr gefragt, wenn die Applikation beendet ist. Dann sollte auch der endlose Thread beendet werden. Um das auszudrücken, erhält ein im Hintergrund arbeitender Thread eine spezielle Kennung: Der Thread wird als Dämon2  gekennzeichnet. Standardmäßig ist ein Thread kein Dämon Threads können Prioritäten zugeordnet werden

19 Lebenszyklus von Java-Threads
Bei einem Thread-Exemplar können wir einige Zustände feststellen: Noch nicht erzeugt, laufend (vom Scheduler berücksichtigt), nicht laufend (vom Scheduler nicht berücksichtigt), wartend und beendet. Wie das Leben eines Thread-Objekts beginnt, haben wir schon kennen gelernt – es muss nur mit new erzeugt werden, ist aber noch nicht im Zustand ausführend. Durch start() gelangt der Thread in den Zustand ausführbar bzw. laufend. Der Zustand kann sich ändern, wenn ein anderer Thread in einem Einprozessorsystem zur Ausführung gelangt und dann dem aktuellen Thread den Prozessor entzieht. Dann geht der vorherige Thread für einen kurzen Moment in den wartenden Zustand. Dieser wird auch erreicht, wenn wir mittels spezieller Synchronisationstechniken in einem Wartezustand verweilen. Nachdem die Aktivität des Thread-Objekts beendet wurde, kann es nicht mehr aktiviert werden und es ist tot, also beendet.

20 Synchron oder Asynchron ?
Nebenläufige Threads arbeiten asynchron Man kann jedoch mit der Methode join() auf das Ende der Aktivität eines Threads warten Beispiel: class JoinTheThread { static class JoinerThread extends Thread { public int result; public void run() { result = 1; } } public static void main( String args[] ) throws Exception { JoinerThread t = new JoinerThread(); t.start(); t.join(); // hier wird gewartet bis t fertig ist System.out.println( t.result ); } } Ohne den Aufruf von join() wird als Ergebnis 0 ausgegeben, denn das Starten des Threads kostet etwas Zeit. In dieser Zeit aber geben wir die nicht initialisierte Klassenvariable aus. Nehmen wir join() hinein, wird die run()-Methode zu Ende ausgeführt und der Thread setzt die Variable result auf 1. Das sehen wir dann auf dem Bildschirm. final void join() throws InterruptedException Der aktuell ausgeführte Thread wartet auf den Thread, für den die Methode aufgerufen wird, bis dieser beendet ist. final synchronized void join( long millis ) throws InterruptedException Wie join(), doch wartet diese Variante höchstens millis Millisekunden. Wurde der Thread bis dahin nicht vollständig beendet, fährt das Programm fort. Auf diese Weise kann versucht werden, innerhalb einer bestimmten Zeitspanne auf den Thread zu warten, sonst aber weiterzumachen. Ist millis gleich 0, so hat dies die gleiche Wirkung wie join(). final synchronized void join ( long millis, int nanos ) throws InterruptedException Wie join(long), jedoch mit potenziell genauerer Angabe der maximalen Wartezeit.

21 Synchronisation mit „synchronized“
Problem: mehrere Threads wollen (gleichzeitig) auf gemeinsamen Daten schreiben Folge: inkonsistente Daten Lösung: zusammenhängende Programmblöcke, die nicht unterbrochen werden dürfen (kritische Abschnitte + gegenseitigen Ausschluss), ABER: Gefahr von Verklemmungen! Beispiel: Ein Zähler der von mehreren Threads heraufgezählt wird: public class Counter { static int counter = 0; private static Counter _counter=null; private Counter() {} public static Counter getInstance() { if (_counter==null) {_counter = new Counter();} return _counter; } public int get_counter() { return counter; public synchronized void inc_counter() { counter++; <- ist nicht atomar!!! Ein Thread kann keine Ergebnisse wie eine Funktion nach außen geben. Die run()-Methode hat den Ergebnistyp void, und da ein nebenläufiger Thread asynchron arbeitet, wissen wir noch nicht einmal, wann wir das Ergebnis erwarten können. Die Übertragung von Werten ist jedoch kein Problem. Hier können Klassenvariablen und auch Objektvariablen helfen, denn über sie können wir kommunizieren. Jetzt fehlt nur noch, dass wir auf das Ende der Aktivität eines Threads warten können. Das geht mit der Methode join(). Gemeinsam genutzte Daten Ein Thread besitzt zum einen seine eigenen Variablen, etwa die Objektvariablen, kann aber auch statische Variablen nutzen, wie das folgende Beispiel zeigt: class T extends Thread { static int result; public void run() { ... } } In diesem Fall können verschiedene Exemplare der Klasse T, die jeweils einen Thread bilden, Daten austauschen, in dem sie die Informationen in result ablegen oder daraus entnehmen. Threads können aber auch an einer zentralen Stelle eine Datenstruktur erfragen und dort Informationen entnehmen oder Zugriff auf gemeinsame Objekte über eine Referenz bekommen. Es gibt also viele Möglichkeiten, wie sich Threads – und damit potenziell parallel ablaufende Aktivitäten – Daten austauschen können. Die Probleme haben ihren Ursprung in der Art und Weise wie die Threads umgeschaltet werden. Der Scheduler unterbricht zu einem für uns unbekannten Zeitpunkt die Abarbeitung eines Threads und lässt den nächsten arbeiten. Wenn nun der erste Thread gerade Programmzeilen abarbeitet, die zusammengehören, und der zweite Thread beginnt parallel auf diesen Daten zu arbeiten, ist der Ärger vorprogrammiert. Zusammenhängende Programmblöcke, die nicht unterbrochen werden dürfen und besonders geschützt werden müssen, nennen sich kritische Abschnitte. Wenn lediglich ein Thread den Programmteil abarbeitet, dann nennen wir dies gegenseitigen Ausschluss oder atomar Bei einem Konflikt (mehrere Threads rufen inc_counter() auf) verhindert synchronized, dass sich mehr als ein Thread gleichzeitig im kritischen Abschnitt, dem Rumpf der Methode inc_counter(), befinden kann. Dies bezieht sich nur auf mehrere Aufrufe von inc_counter() für dasselbe Objekt. Zwei verschiedene Threads können durchaus parallel die Methode inc_counter() für unterschiedliche Objekte ausführen. Für die Klassenvariable counter löst eine synchronized Objektmethode aber nicht alle Zugriffsprobleme (daher das singleton-Entwurfsschema). Analogie (zum kritischen Abschnitt): Synchronized <-> sperrt Toilettentür ab, bis der auf‘m Klo fertig ist Dann machen wir doch gleich alles synchronized ... Probleme: Methoden, die synchronisiert sind, müssen von der JVM besonders bedacht werden, damit keine zwei Threads die Methode für das gleiche Objekt ausführen. Wenn also ein zweiter Thread in die Methode eintreten möchte, kann er das nicht einfach machen, sondern muss vielleicht erst neben vielen anderen Threads warten. Es muss also eine Datenstruktur geben, in der wartende Threads eingetragen und ausgewählt werden. Das kostet zusätzlich Zeit und ist im Vergleich zu einem normalen Methodenaufruf viel teurer. Zusätzlich kommt als Problem hinzu, wenn eine nicht notwendigerweise, also überflüssige, synchronisierte Methode eine Endlosschleife oder lange Operationen durchführt. Dann warten alle anderen Threads auf die Freigabe und das kann im Fall der Endlosschleife ewig sein. Auch bei Multiprozessorsystemen profitieren wir nicht von dieser Programmiertechnik. Das synchronized macht die Vorteile von Mehrprozessormaschinen zunichte. Wenn alle Methoden synchronisiert sind, dann steigt auch die Gefahr eines unnötigen Deadlocks. Jedes Java-Objekt definiert einen so genannten Monitor (Der Begriff geht auf C.A.R. Hoare zurück, der im Aufsatz »Communicating Sequential Processes« von 1978 erstmals dieses Konzept veröffentlichte.) Wenn wir nichts synchronisieren, dann benutzen wir den Monitor auch nicht. Er kann auch nicht direkt genutzt werden, sondern ist eine interne Eigenschaft. Mit dem Monitor – wir nennen ihn auch Lock (leicht zu merken durch einen Schlüssel, der die Tür abschließt) – ist der serielle Zugriff gewährleistet. Wir setzen dann vor die Methode das Schlüsselwort synchronized. Ein betretender Thread setzt dann diesen binären Monitor des aufrufenden Objekts, und andere ankommende Threads müssen warten, wenn dieser gesetzt ist In manchen Fällen ist das Synchronisieren einer gesamten Funktion etwas viel und nur ein oder zwei Anweisungen bilden den kritischen Abschnitt. Dann kann eine allgemeinere Variante in Java eingesetzt werden, die nur einen Block synchronisiert. Dazu schreiben wir in Java Folgendes: synchronized( objektMitDemMonitor ) { ... } Der Block wird in die geforderten Klammern gesetzt, und hinter dem Schlüsselwort in Klammern muss ein Objekt stehen, das den zu verwendenden Monitor besitzt. Die Konsequenz ist, dass über einen beliebigen Monitor synchronisiert werden kann, und nicht unbedingt über den Monitor des Objekts für das die synchronisierte Methode aufgerufen wurde, so wie es bei synchronisierten Objektmethoden normal ist. z.B. Point p = new Point(); synchronized( p ) { p.setLocation( 1, 2 ); Deadlocks können auch in Java nicht erkannt und verhindert werden. Uns fällt also die Aufgabe zu, diesen ungünstigen Zustand erst gar nicht herbeizuführen.

22 Synchronisation über Warten und Benachrichtigen
public class Fernseher { public Fernseher() {} public boolean is_sport() {...} } public class Frau extends Thread { public void run() { synchronized(_fernseher) { _fernseher.wait(); // Sportschau fängt an! public class Mann extends Thread { while (!_fernseher.is_sport()) {} _fernseher.notify(); Erzeuger-Verbraucher-Programm Beispiel Wir haben zwei Threads, die sich am Objekt o synchronisieren. Thread T1 wartet auf Daten, die Thread T2 liefert. In T1 finden wir dann etwa folgenden Programmcode: synchronized( o ) { o.wait(); // Habe gewartet, kann jetzt loslegen. } Und T2, der etwas liefert, schreibt Folgendes: // Habe etwas gemacht und informiere jetzt meinen Wartenden. o.notify(); } Die Synchronisation von Methoden oder Blöcken ist eine einfache Möglichkeit, konkurrierende Zugriffe von der virtuellen Maschine auflösen zu lassen. Obwohl die Umsetzung mit den Locks die Programmierung einfach macht, reicht es für viele Aufgabenstellungen nicht aus. Wir können zwar Daten austauschen, doch gewünscht ist dieser Austausch in einer synchronisierten Abfolge. Gerne möchte ein Thread das Ankommen von Informationen signalisieren. Andere Threads wollen informiert werden, wenn Daten ankommen. In Java wird dies durch die speziellen Objektmethoden wait() und notify() realisiert. Sie sind in der Klasse Object definiert (also hat sie jedes Objekt) und werden vom Thread aufgerufen, der gerade den Lock besitzt. Den kann er nur dann besitzen, wenn er sich in einem synchronisierten Block aufhält Wenn wir den Lock am Objekt o festmachen und warten, dann schreiben wir: try { } catch ( InterruptedException e ) {} } Ein wait() kann mit einer InterruptedException vorzeitig abbrechen, wenn der wartende Thread per interrupt()-Methode unterbrochen wird. Während des Wartens wird der Monitor des Objekts freigegeben, damit benachrichtigende Threads auf dieses Objekt synchronisieren können. Wenn das aktuelle Thread das Lock eines Objekts besitzt, kann er Threads aufwecken, die auf dieses Objekt warten. Er bekommt den Lock durch das Synchronisieren der Methode, was ja bei Objektmethoden synchronized(this) entspricht. synchronized void benachrichtige() {notifyAll(); } Um notify() muss es keinen eine Exception auffangenden Block geben Wartet ein Thread auf eine Nachricht, dann versetzt er sich mit wait() in eine Art Trance. Er geht in den Zustand nicht ausführend über und gibt für diesen Zeitraum seinen Monitor frei. Würde der wait() aufrufende Thread den Monitor nicht freigegeben, könnte der auch nicht von anderen Threads reserviert werden. Somit könnte kein anderer Thread synchronisierten Programmcode ausführen, da ja der Monitor belegt ist und normalerweise sichergestellt sein muss, dass sich nur einer im kritischen Abschnitt befindet. Doch in dem speziellen Fall kann nach dem wait() ein anderer Thread in eine synchronisierte Methode oder einen synchronisierten Block eintreten, die bzw. der über den Monitor des Objekts synchronisiert. Dort kann der wartende Thread (es können auch mehrere sein) aufgeweckt werden. Dazu ruft der weckende notify() auf. Das heißt, die Methoden kommen immer paarweise vor: Wo es ein wait() gibt, da darf auch ein notify() nicht fehlen. Nach einem notify() sollten wir den synchronisierten Abschnitt beenden und den Lock wieder freigegeben. Der wartende Thread wird nun wieder aufgeweckt, bekommt seinen Monitor zurück und kann weiterarbeiten. Wenn mehrere Threads gewartet haben, dann wählt ein Algorithmus zufällig einen Thread aus und weckt ihn auf.

23 Class java.lang.Thread (Methoden) (1)
void start() ein neuer Thread, neben dem die Methode aufrufenden Thread, wird gestartet. Der neue Thread führt die run()-Methode nebenläufig aus. void run() diese Methode enthält den parallel auszuführenden Programmcode boolean isAlive() liefert true, wenn der Thread gestartet und noch nicht terminiert ist void sleep(long millis, int nanos) der aktuell ausgeführte Thread wird mindestens millis Millisekunden und zusätzlich nanos Nanosekunden eingeschläfert. void yield() zwingt den aktuell ausgeführten Thread zu pausieren und erlaubt die Weiterführung anderer Threads void wait() der aktuelle Thread wartet an dem aufrufenden Objekt darauf, dass er nach einem notify() weiterarbeiten kann void notify() weckt einen beliebigen Thread auf, der an diesem Objekt wartet void notifyAll() weckt alle Threads auf, die an diesem Objekt warten void join() der aktuell ausgeführte Thread wartet auf den Thread, für den die Methode aufgerufen wird, bis dieser beendet ist void interrupt() setzt in einem (anderen) Thread-Objekt ein Flag, um den Thread zu beenden boolean isInterrupted() fragt das Flag ab boolean interrupted() testet das entsprechende Flag des aktuell laufenden Threads, und modifiziert es auch, sodass es danach gelöscht ist ThreadDeath ist eine Unterklasse von Error. Error ist wiederum von Throwable abgeleitet, sodass ThreadDeath mit einem try/catch-Block abgefangen werden kann Wenn wir ThreadDeath auffangen, dann können wir noch auf den Tod reagieren und Aufräumarbeiten machen void destroy() Destroys this thread, without any cleanup

24 Class java.lang.Thread (Methoden) (2)
Seit JDK 1.2 verworfen und als „deprecated“ eingestuft: void suspend() blockiert den Thread void resume() de-blockiert den Thread void stop() beendet den Thread deprecated = missbilligt Sowohl suspend() als auch stop() zwingen den Thread augenblicklich aufzuhören. Dies kann an jeder belieben Stelle im Quellcode geschehen und kann somit dazu führen, dass inkonsistente Zustände (von Objekten) hinterlassen werden.

25 Remote Method Invocation (RMI)
RMI ist für Java der Mechanismus, um entfernte Objekte und dessen Angebote zu nutzen RMI (Java) steht im Gegensatz zu dem komplexen CORBA (unterschiedliche Programmiersprachen) Anpassung von RMI an den defacto Standard CORBA Somit lässt sich auch über RMI eine Verbindung zwischen Java-Programmen und nicht Java-Programmen herstellen. Neben der reinen Java-Lösung RMI gibt es auf dem großen Land der Standards das komplexe CORBA. Im Gegensatz zu RMI definiert CORBA ein großes Framework für unterschiedliche Programmiersprachen. Die Frage nach dem Sinn von RMI ist also erlaubt. Die Antwort liegt jedoch in der Einfachheit und Integration von RMI. In den letzen Jahren hat Sun jedoch RMI an den defacto Standard CORBA angepasst. Damit lässt sich auch über RMI eine Verbindung zwischen Java-Programmen und nicht Java-Programmen herstellen. Java RMI ist nun der Mechanismus, um entfernte Objekte und dessen Angebote zu nutzen. Auch schon der Vorgänger in der prozeduralen Welt, RPC (Remote Procedure Call), ist eine Entwicklung von Sun. Mit RMI lässt sich somit auf hohem Abstraktionsniveau arbeiten.

26 CORBA CORBA steht für Common Object Request Broker Architecture
Entwickelt von der Object Management Group (OMG) Beruht in seinen Grundzügen auf der vom Remote Procedure Call bekannten Technik Standardisierung von Object Request Broker (ORB) ORB ist die Basiskomponente der Kommunikation in verteilten Anwendungen. Er dient dazu, Client/Server-Beziehungen zwischen Objekten aufzubauen. CORBA verfügbar u.a. in C, C++, Java, COBOL, Smalltalk, Ada, Lisp, Python. Die Object Management Group (OMG) ist ein Zusammenschluß von Hardwareherstellern, Softwareentwicklern, Netzwerkbetreibern und kommerziellen Nutzern von Software. Sie wurde 1989 von 8 Firmen gegründet, öffnete sich aber nach wenigen Monaten als unabhängige Organisation für andere Unternehmen. Bis Anfang 1998 haben sich der OMG über 800 Unternehmen angeschlossen. Der Object Request Broker (ORB) bildet die Basiskomponente für die Kommunikation in verteilten Anwendungen. Er dient dazu, Client/Server-Beziehungen zwischen Objekten aufzubauen. Ein Client ist dabei ein Objekt, das eine Operation eines anderen Objektes aufrufen möchte. In der Regel werden die Implementierungen mehrerer Objekte zu einem Programm zusammengefaßt, das dann als Server fungiert. Der Mechanismus zur Kommunikation beruht in seinen Grundzügen auf der vom Remote Procedure Call bekannten Technik der lokalen Stellvertreter (Stubs) für die entfernten Objekte und der Umwandlung der Parameter für den Transport. Aus der Standardisierung des ORB ergab sich auch der Name CORBA: Common Object Request Broker Architecture. OMG has standardized mappings from IDL to C, C++, Java, COBOL, Smalltalk, Ada, Lisp, Python, and IDLscript.

27 Vergleich zwischen CORBA und RMI
Viele Ähnlichkeiten in Architektur und Prinzip zwischen RMI und CORBA Beide bieten Mechaniken wie Garbage Collection, Interface Beschreibung und Naming Service RMI wurde speziell für Java zugeschnitten, daher Anschein eines abgespeckten CORBAs

28 Vor- und Nachteile Vorteile zu CORBA Nachteile zu CORBA Transparenz
einfache Programmierung eingebunden in JAVA JDK URL basierte Namensgebung Nachteile zu CORBA auf JAVA beschränkt, d.h. nicht so mächtig proprietäres Protokoll Geschwindigkeitsnachteil wenige Services implementiert (auch nicht vorgesehen) proprietärer Weg im Gegensatz zu standardisierten CORBA ??? Was heißt propietärer ??? Zu Transparenz die by-refvalue Semantik des normalen Java beizubehalten. Damit wird es möglich, verteilungstransparent zu programmieren. Entfernte Zugriffe unterscheiden sich von lokalen syntaktisch und semantisch nicht ABER: Verteilungstransparenz sollte also nicht so weit gehen, vor dem Programmierer zu verstecken, ob ein Objekt by-refvalue oder by-copy übergeben wird Z.B.: Aufruf der equals-Methode an einem entfernten Objekt. Welche wird ausgefürt? Die lokale (von Objekt geerbt) oder die entfernte Methode (eventuell neu implementiert, dann sinnvoll)?

29 Funktionsweise von RMI
Der Server stellt das entfernte Objekt mit der Funktion bereit. Die Funktion läuft im eigenen Adressraum, und der Server leitet Anfragen an diese Funktion weiter. Eine Schnittstelle spezifiziert die von den entfernten Objekten bereitgestellten Methoden. Die Methoden der Schnittstelle müssen dann implementiert werden. Der RMI-Compiler „rmic“ von Java generiert die Stubs und Skeletons. Über den Namendienst (Registry) melden die Server ihre entfernten Objekte mit einem Namen an. Der Client holt sich über die Registry das Objekt mit der gewünschten Methode. Damit der Client eine entfernte Methode nutzen kann, muss er ein Stellvertreterobjekt befragen. Dieses packt die Daten ein und übermittelt sie. Damit der Generator korrekten Quellcode für die Übertragung erstellen kann, ist eine Beschreibung nötig. Die Definition muss die Signatur eindeutig spezifizieren, und damit weiß der Client, wie die Funktion aussieht, die er aufrufen kann und der Server kann die Methode dann beschreiben. Die Schnittstelle erweitert die Schnittstelle Remote. Remote ist allerdings leer und damit eine Markierungsschnittstelle. Zuerst fällt in der Implementierung auf, dass wir die Klasse UnicastRemoteObject erweitern. Sie liegt im Paket java.rmi.server und zeigt so die grobe Richtung für die Verwendung an. Bevor rmic zum Zuge kommt, müssen die entfernten Klassen und Schnittstellen übersetzt sein. Mit „$rmic AdderImpl“ werden die Klassen erzeugt. RMI gibt es mittlerweile in unterschiedlichen Version. Mit dem Schalter -vXXX bzw. –iiop (for CORBA) lässt sich dies genauer angeben. Mit dem Namensdienst können die Server ihre entfernten Objekte mit einem Namen anmelden. Er hilft außer den Clients, die entfernten Objekte zu finden. Unter Windows starten wir den Dienst im Hintergrund mit folgender Zeile: $start rmiregistry Der Server ist ein normales Java-Programm ohne Einschränkungen. Er muss weder etwas mit Remote noch mit Serializable zu schaffen haben. Seine einzige Aufgabe ist es, ein entferntes Objekt anzulegen und beim Namensdienst einzutragen. Dazu wird die Methode rebind() oder bind() benutzt. Ebenso wie der Server ist der Client ein normales Java-Programm, welches weder etwas mit Remote noch mit Serializable zu tun hat. Um nun die entfernte Methode zu nutzen, muss ein entferntes Objekt gesucht und angesprochen werden. Dazu fragen wir den Namensdienst.

30 Die Schnittstelle import java.rmi.*;
public interface Adder extends Remote { public int add(int x, int y) throws RemoteException; } Übergabe von primitiven Parametern unproblematisch Übergabe von serialisierbaren Objekten unproblematisch, falls Klassen beiden Seiten bekannt sind, sonst müssen diese Klassen über den RMI-Klassenlader nachgeladen werden. Die entfernte Schnittstelle ist öffentlich. Wenn sie nur paketsichtbar oder eingeschränkter ist, kann der Client die entfernte Methode nicht finden, wenn er danach verlangt.   Die eigene Schnittstelle erweitert die Schnittstelle Remote. Nur die Klassen, die Remote implementieren, können entfernte Methoden anbieten. Remote ist allerdings leer und damit eine Markierungsschnittstelle. Die angebotenen Methoden können nicht beabsichtigte Fehler auslösen, zum Beispiel, wenn das Transportsystem zusammenbricht. Für diesen Fall muss jede Methode RemoteException in einer throws-Anweisung aufführen. Eine entfernte Funktion darf Parameter besitzen. Sind dies primitive Werte, so werden diese einfach übertragen. Handelt es sich um Objekte, so müssen diese serialisierbar sein. Klassen, die auf beiden Seiten vorliegen, weil es zum Beispiel Klassen aus dem Standard-API sind. Klassen, die nur auf der Server-Seite vorliegen und dem Client nicht bekannt sind. Klassen, die selbst wieder Remote implementieren.

31 Die Implementation import java.rmi.*; import java.rmi.server.*; public class AdderImpl extends UnicastRemoteObject implements Adder { public AdderImpl() throws RemoteException { } public int add( int x, int y ) throws RemoteException { return x + y; } } Übertagung der Daten mittels Standard-TCP-Sockets Der Standard-Konstruktor ist notwendig, da eine Unterklasse genau diesen aufrufen möchte. Unser Konstruktor muss nichts machen. Er ruft aber automatisch den Konstruktor der Oberklasse auf, also den von UnicastRemoteObject. Wenn wir uns dafür entscheiden, keine Objekte über UnicastRemoteObject anzubieten, aber eine existierende Klasse trotzdem Anbieter sein möchte, so muss das Objekt im Konstruktor ein entferntes Objekt, welches Remote implementiert, mit UnicastRemoteObject.exportObject(Remote) anmelden. public static void main( String args[] ) throws Exception { Server server = new Server(); UnicastRemoteObject.exportObject( server ); Naming.rebind( "echo", server ); }

32 RMIC

33 Stubs und Skeletons (1) Stubs ist der Stellvertreter für den Server auf der Client-Seite Skeletons ist der Stellvertreters des Clients auf der Server-Seite Sind die Objekte die wirklich die Kommunikation betreiben Ein Stub ist ein Stellvertreter (client-seitiger Proxy) für das entfernte Objekt auf der Client-Seite, der die RMI-Anfragen an den Skeleton (server-seitig) weitergibt. Der Skeleton richtet die Client-Anfrage an die wirkliche Methodenimplementierung und schickt das Ergebnis wieder zurück. Die Stellvertreter sind Methoden auf der Client- und Server-Seite, die die tatsächliche Kommunikation betreiben.

34 Stubs und Skeletons (2) 1. Schicht: Kommunikationspartner für Client und Server 2. Schicht: Regelt die korrekte Ausführung der Operationen 3. Schicht: Regelt die Netzkommunikation 4. Schicht: (Internet-)Verbindungsprotokolle Ein Stub ist ein Stellvertreter (client-seitiger Proxy) für das entfernte Objekt auf der Client-Seite, der die RMI-Anfragen an den Skeleton (server-seitig) weitergibt. Der Skeleton richtet die Client-Anfrage an die wirkliche Methodenimplementierung und schickt das Ergebnis wieder zurück. Die Stellvertreter sind Methoden auf der Client- und Server-Seite, die die tatsächliche Kommunikation betreiben. 2. Schicht: Ist verantwortlich dafür, dass eine Operation korrekt ausgeführt wird. Hier werden die Anfragen an Remote Objekte verwaltet und andere Spezialfälle gehandthabt. 3.Schicht: Der Programmierer muss sich nicht mehr darum kümmern

35 Der Server import java.net.*; import java.rmi.*;
import java.rmi.server.*; import java.rmi.registry.*; public class AdderServer { public static void main( String args[] ) throws Exception { AdderImpl adder = new AdderImpl(); Naming.rebind( "Adder", adder ); System.out.println( "Adder bound" ); } Der Namensdienst läuft und wartet auf den Server und den Client. Beginnen wir mit dem Server. Er ist ein normales Java-Programm ohne Einschränkungen. Er muss weder etwas mit Remote noch mit Serializable zu schaffen haben. Seine einzige Aufgabe ist es, ein entferntes Objekt anzulegen und beim Namensdienst einzutragen. Dazu wird die Methode rebind() oder bind() benutzt. Automatisches Anmelden bei Bedarf  Wir leiten unser Objekt dann von der Klasse Activatable ab, und dann werden die Objekte bis zu ihrer Aktivierung in einem Dämmerzustand gehalten. Kommt dann der erste Zustand, entfaltet das System dieses Objekt, sodass es Anfragen entgegennehmen kann. Wird das Objekt nach seiner Tat wiederum nicht verwendet, kann es wieder eingefroren werden. Die Daten bleiben dabei stabil.

36 Der Client import java.rmi.*; import java.rmi.registry.*;
import java.rmi.server.*; public class AdderClient { public static void main( String args[] ) { try { Adder a=(Adder)Naming.lookup("Adder"); int sum = a.add( 2, 2 ); System.out.println( sum ); } catch (Exception e) {System.out.println( e );} Naming.lookup() liefert zu einer URL ein Stub-Objekt, welches die gewünschte Schnittstelle Adder implementiert. Der Rückgabetyp ist Remote.

37 RMI-Methoden protected UnicastRemoteObject() Erzeugt und exportiert ein neues UnicastRemoteObject und bindet es an einen unbekannten Port. static RemoteStub exportObject( Remote obj ) Exportiert das entfernte Objekt und macht es empfänglich für einkommende Aufrufe. Es wird ein willkürlicher Port verwendet. static void bind( String name, Remote obj ) Bindet den Stub, an den Namen name und trägt es so in der Registrierung ein. static void rebind( String name, Remote obj ) Wie bind(), nur dass Objekte ersetzt werden, falls sie schon angemeldet sind. static void unbind( String name ) Entfernt das Objekt aus der Registrierung. static Remote lookup( String name ) Liefert eine Referenz auf den Stub, der mit dem entfernten Objekt name verbunden ist. static String[] list( String name ) Liefert ein Feld mit angemeldeten Diensten. Der angegebene Name gibt die URL des Namensdienstes an. static void bind( String name, Remote obj ) throws AlreadyBoundException, MalformedURLException, RemoteException Bindet das Objekt ref, welches in der Regel der Stub ist, an den Namen name und trägt es so in der Registrierung ein. Eine AlreadyBoundException zeigt an, dass der Name schon vergeben ist. Die MalformedURLException informiert, wenn der Name ungültig gebunden ist. Eine RemoteException wird ausgelöst, wenn der Namensdienst nicht erreicht werden konnte. Fehlende Rechte führen zu einer AccessException. static void unbind( String name ) Entfernt das Objekt aus der Registierung. Ist das Objekt nicht gebunden, so folgt eine NotBoundException. Die anderen Fehler sind wie bei bind(). static Remote lookup( String name ) throws NotBoundException, MalformedURLException, RemoteException Liefert eine Referenz auf den Stub, der mit dem entfernten Objekt name verbunden ist. War kein Dienst unter dem Namen verfügbar, kommt es zu einer NotBoundException. Ist der Namensdienst nicht erreichbar, folgt eine RemoteException. MalformedURLException kann durch eine falsch gebildete URL folgen. static String[] list( String name ) Liefert ein Feld mit angemeldeten Diensten. Der angegebene Name gibt die URL des Namensdienstes an. Ist die URL falsch konstruiert, so folgt eine MalformedURLException; ist die Registry nicht erreichbar, folgt eine RemoteException.

38 Jini Java Intelligence Network Infrastructure
Offiziell wurde Jini am 25. Januar 1999 von Sun Microsystems vorgestellt Erweiterung der Java-Plattform (Java 2) Netzwerktechnologie, die es ermöglicht, Ressourcen im Netzwerk dynamisch zu verwalten Auffinden von Diensten zu regeln Geräte untereinander zu verknüpfen Hauptmerkmale von Jini: Plattform-Unabhängigkeit nur Dienste (Services) keine Unterscheidung zwischen Hard- und Software Jini kann selbst Basis für weitere Netzdienste (wie JavaSpaces) sein Infrastruktur: drahtgebunden oder drahtlos Kommunikation: beliebiges Protokoll, standardmässig: RMI Jini erweitert die Idee hinter RMI so, dass ein Ort zur Verfügung gestellt wird, an dem die Objekte ihre Services anbieten oder andere Services finden können Offiziell wurde Jini am 25. Januar 1999 von Sun Microsystems vorgestellt Jini ist eine Erweiterung der Java-Plattform und basiert auf der Technik von Java 2 Jini basiert auf der Java-Plattform und kann selbst Basis für weitere Netzdienste wie JavaSpaces sein Jini[tm] ist eine Netzwerktechnologie, die es - basierend auf der Java[tm]-Technologie - ermöglicht, Geräte untereinander zu verknüpfen. In einem Jini-Netzwerk, ist alles ein Dienst (Service). Hardware und Software werden demzufolge lediglich als zwei verschiedene Weisen, einen Service zu implementieren, angesehen. Dies ist eines der Hauptmerkmale von Jini Geräte nutzen, ohne explizite Treibersoftware installieren zu müssen Jini ist ein Protokoll zur Verwaltung von Ressourcen im Netzwerk. Dabei kann eine Komponente sowohl Hardware, als auch Software sein. Man spricht dabei allgemein von "Diensten" (Videorecorder, Fernsehbildschirm, CD-Player, SAT-Tuner usw., alle Geräte vom Mikrowellenherd bis zum Drucker, Auch ein Stück Software kann einen Dienst bereitstellen, wie z. B. ein Dictionary-Server oder gar eine Rechenkapazität) Jini übernimmt die Verwaltung der Ressourcen im Netz und regelt das Auffinden von Diensten In einem Jini-Netzwerk gibt es drei verschiedene Gruppen: Dienst, Client, Lookup-Service Basierend auf der Java-Plattform soll das System beliebige Ressourcen netzweit dynamisch verwalten. Ob es um die netzweite Einbindung eines Druckers geht, um den Blick über eine Kamera ins heimische Kinderzimmer oder um den verteilten Zugriff auf eine neue Applikation: zukünftig soll sich die in Arbeit befindliche Klassenbibliothek Jini um die Kommunikationsdetails kümmern. Für Jini spricht außer seinem hohen Abstraktionsniveau, daß es auf Java aufsetzt, plattformunabhängig ist und auf beliebiger Hardware laufen kann. Da der Kern nur 48 KByte groß ist, wäre theoretisch auch eine Installation auf einem Drucker oder einer Settop-Box denkbar. Nicht nur die Unterscheidung zwischen Hardware und Software wird von Jini aufgehoben. Da Jini auf der Java-Technologie basiert, ist die Plattform-Unabhängigkeit gewährleistet. Die von Jini genutzte Infrastruktur kann sowohl drahtgebunden als auch drahtlos sein und zur Kommunikation kann ein beliebiges Protokoll gewählt werden. Aus nahe liegenden Gründen benutzt Jini standardmässig Java Remote Method Invocation (RMI), um ausführbaren Code als Objekt über das Netzwerk zu verschicken. RMI basiert auf mobilen Objekten, die Interfaces implementieren, die auch anderen Objekten bekannt sind. So kann eine Kommunikation mit Objekten, die auf anderen Maschinen beherbergt sind, stattfinden. Jini erweitert die Idee hinter RMI insofern, als ein Ort zur Verfügung gestellt wird, an dem die Objekte ihre Services anbieten oder andere Services finden können. Dienste: Videorecorder, Fernsehbildschirm, CD-Player, SAT-Tuner usw., alle Geräte vom Mikrowellenherd bis zum Drucker, auch ein Stück Software kann einen Dienst bereitstellen, wie z. B. ein Dictionary-Server oder gar eine Rechenkapazität; alle Nutzungsmöglichkeiten von Applikationen, Datenbanken, Betriebssystemen, Servern, mobilen Geräten, Druckern, Massenspeichern, Handheld-Geräten sowie zahllose andere Services, auf die über das Netzwerk zugegriffen werden kann.

39 Funktionsweise von Jini
In einem Jini-Netzwerk gibt es drei verschiedene Gruppen: Dienst, Client, Lookup-Service Ein Service muss sich im Lookup Service registrieren Ein Client sucht einen Service (Dienst) über den Lookup Service Beide müssen allerdings zuerst den Lookup Service finden. Das geschieht über das Discovery. Der Anbieter eines Dienstes fragt zunächst nach einem Lookup Service, indem er seine Anwesenheit über einen Broadcast ankündigt. Hat er den Service gefunden, schickt er ihm einen Proxy mit den eigenen Schnittstellen als Java Interface sowie allen anderen beschreibenden Attributen. Nachdem der Lookup Service den neuen Dienst registriert hat, können Benutzer oder andere Dienste darauf zugreifen. Die Registrierung eines Dienstes beim Lookup Service heißt ‘Discovery’, da ihn Jini in dem Moment quasi ‘entdeckt’. Ein Client sucht einen Service über den Lookup Service. Ein Service muss sich im Lookup Service registrieren. Beide müssen allerdings zuerst den Lookup Service finden. Das geschieht über das Discovery. Wird ein Dienst Teil eines Jini-Verbunds, zum Beispiel durch den Anschluß eines Gerätes ans Netz, kommt das Discovery-Protokoll zum Einsatz. Möchte dagegen ein Benutzer oder ein Client einen Dienst über den Lookup Service finden und aufrufen, geschieht dies auf Basis des gleichnamigen Protokolls. Ein Client findet den gewünschten Dienst über dessen Typ, das heißt über sein Java-Interface beziehungsweise das Java-Typ-System. Ist eine Benutzerschnittstelle vorhanden, lassen sich zusätzlich beschreibende Attribute für die Auswahl angegeben. Für den Aufruf des Dienstes lädt der Lookup Service als RMI-Server den zugehörigen Proxy in den Client. Dies geschieht, indem der Service den Code des Objekts für den Client transparent überträgt, allerdings nur beim ersten Mal. Beim nächsten Aufruf ist das zum Objekt gehörige Class File auf dem Client vorhanden, und die vom Service gelieferte Referenz verweist auf das dort gefundene Objekt. Der Proxy kann ein privates Protokoll für die Kommunikation mit dem Dienstanbieter verwenden, so daß man bisher eingesetzte proprietäre Prokolle beibehalten kann, um Geräte oder Programme mit Jini zu verbinden. Verschiedene Realisierungen desselben Dienstes dürfen sogar mit unterschiedlichen Protokollen arbeiten.

40 Grundkonzepte von Jini
Discovery: das Finden von Communities im Netz und deren Verbindung untereinander (als spontane Community-Bildung) Lookup: diese Rolle des Directoryservices in der Jini-Community ist das Suchen und Finden von Diensten unter Berücksichtigung der Typenhierarchie und Vererbungsrelation Leasing: dieses Konzept impliziert die sogenannte Selbstheilungsfähigkeit im Jini und bedeutet, daß nach einem möglichen Ausfall von Diensten das Recover gesteuert wird und das Wachstum nicht benötigter Dienste in Grenzen gehalten wird (vgl. Garbage Collection) Remote Events: das bedeutet, daß die Dienste einander Mitteilungen über ihre Statusänderungen machen können (jede Jini-Komponente kann im Prinzip alle (anderen) Events empfangen) Transactions: hierbei geht es um die sichere Realisierung von Transaktionen, die eine verteilte Ausführung letztendlich erfordert Für die meisten Dienste legt eine ‘Lease’ die Dauer des Zugriffs fest. Die genauen Bedingungen handeln Benutzer und Anbieter eines Dienstes als Teil des Service Protocol aus. Nachdem der Benutzer den Service angefordert hat, wird ihm der Zugriff gewährt - im Idealfall unter Berücksichtigung des von ihm gewünschten Zeitraums. Soll eine Lease nach dessen Ablauf verlängert werden, ist erneutes Verhandeln angesagt. Stellt ein Service zum Beispiel einen Drucker zur Verfügung, kann er ihn nach Ablauf einer Lease einem anderen Client anbieten, wenn der erste Client ihn nicht erneut anfordert. Der Service wartet nicht darauf, daß der Client explizit sagt, daß er den Drucker nicht mehr benötigt. Wichtig ist dieses Vorgehen, wenn die Verbindung zwischen Client und Service unterbrochen wird, der Client also keine Möglichkeit mehr hat, mitzuteilen, daß er die Ressource nicht mehr braucht. Ein Lease kann exklusiv sein oder für mehrere Benutzer gelten. Im ersten Fall wird ein exklusier Zugriff auf eine Ressource garantiert, andernfalls können sich mehrere Benutzer eine teilen.

41 JavaSpaces (1) JavaSpaces basiert auf der Jini-Technologie (spezieller Jini-Service) Entstanden aus den „Tuple-Spaces“ von „Linda“ Ein Space ist ein fortdauerndes Objekt-Lager, das von verschiedenen Prozessen gemeinsam genutzt wird und über das Netzwerk zugänglich ist Prozesse kooperieren, indem Objekte (Entrys) Spaces betreten und wieder verlassen Dadurch nur schwach mit einander verbundene Prozesse Jegliche Kommunikation, sowie die - besonders bei verteilten Systemen - schwierige Synchronisation der Aktivitäten geschieht mit Hilfe des Space Die JavaSpaces sind aus den "Tuple-Spaces" entstanden, die im Zusammenhang mit der Programmiersprache "Linda" im Jahr 1982 das Licht der Welt erblickten. Die JavaSpaces-Technologie basiert auf der Jini-Technologie, sie ist eigentlich nur ein spezieller Jini-Service. Im Verständnis der JavaSpaces-Technologie ist eine Applikation eine Sammlung von Prozessen. Diese Prozesse kooperieren, indem Objekte einen oder mehrere sogenannte Spaces betreten und wieder verlassen. Damit wird die Erstellung von verteilter Software erleichtert, denn die Prozesse sind nur sehr schwach miteinander verbunden. Jegliche Kommunikation sowie die - besonders bei verteilten Systemen - schwierige Synchronisation der Aktivitäten geschieht mit Hilfe des Space. Ein Space ist ein fortdauerndes Objekte-Lager, das von verschiedenen Prozessen gemeinsam genutzt wird und über das Netzwerk zugänglich ist. Des weiteren dient ein Space zum Austausch von Objekten, denn die Prozesse kommunizieren nicht direkt miteinander, sondern die Koordination geschieht mittels des Space. Ein Space beherbergt Einträge - Entrys. Die Idee hinter den Entrys ist sehr einfach: Ein Entry ist ein Objekt, das das net.jini.core.entry.Entry-Interface implementiert.

42 JavaSpaces (2) Die Prozesse führen drei einfache Operationen aus:
write: Objekte in den Space schreiben read / readIfExists : Objekte, die sich im Space befinden, lesen take / takeIfExists: Objekte aus dem Space herausnehmen Solange sich ein Objekt im Space befindet, ist es passiv. Um ein Objekt zu verändern, oder ein Methode auf ihm aufzurufen, muss ein Prozess das Objekt explizit aus dem Space entfernen, es eventuell verändern und danach wieder zurücklegen.

43 Das „Hello World“ - Beispiel
import net.jini.core.lease.Lease; import net.jini.space.JavaSpace; public class HelloWorld { public static void main(String[] args) { try { Message msg = new Message(); msg.content = "Hello World"; JavaSpace space = SpaceAccessor.getSpace(); space.write(msg, null, Lease.FOREVER); Message template = new Message(); Message result = (Message)space.read(template, null, Long.MAX_VALUE); System.out.println(result.content); } catch (Exception e) {e.printStackTrace();} } Die write-Methode legt die Kopie eines Entry in den Space. Wird write mehrmals hintereinander mit demselben Entry aufgerufen, so werden mehrere Kopien dieses Entry in den Space befördert. Hiermit wurde ein "Hello World"-Entry in den Space geschrieben. Ihm wurde eine Leasing-Zeit mit auf den Weg gegeben, die nie abläuft Wenn ein Entry im Space existiert, kann ihn jeder Prozess, der Zugriff auf diesen Space hat, lesen. Um einen Entry zu lesen, wird ein Template, eine Schablone, benutzt. Dieses Template ist ein Entry, bei dem ein oder mehrere Felder auf null gesetzt sind. Ein Entry passt auf ein Template, wenn er den gleichen Typ (oder einen Untertyp) wie das Template hat und wenn alle Felder, die nicht auf null gesetzt wurden, genau übereinstimmen. Die null-Felder agieren als Wildcards und eignen sich für jeden Wert. Die read-Operation liefert die Kopie eines im Space vorhandenen passenden Entry Da read lediglich einen Entry zurückgibt, muss er wieder "gecastet" werden Da wir ein Template haben, dessen content-Feld eine Wildcard enthält, wird jeder Message-Entry im Space darauf passen, egal welche Nachricht er enthält. Die read-Operation liefert die Kopie eines im Space vorhandenen passenden Entry. Bei der read-Operation wurde so lange wie irgendwie möglich (Long.MAX_VALUE) auf einen passenden Entry gewartet Die take-Operation funktioniert genauso wie die read-Operation. Der einzige Unterschied ist, dass der Entry, der als passend herausgefunden wurde, aus dem Space herausgenommen wird

44 Quellen Vorlesungen P2 / P3
Galileo- Openbook: Java-API: Weitere Quellen: u.a.

45 Fragen ?


Herunterladen ppt "Java Programmiersprachenkonzepte Kai Meyer & Torsten Witte"

Ähnliche Präsentationen


Google-Anzeigen