Java Programmiersprachenkonzepte Universität HH, SS 2004 29.04.2004 Kai Meyer & Torsten Witte Inhalt • • • • • • • • Ursprünge & Anwendungsgebiete Funktionsweise von Java Datentypen, Operatoren, Kontrollfluss Vererbung, Polymorphie Threads RMI Jini JavaSpace 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 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. 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 Elementare Typen • Ganzzahlige Typen: byte short int long 8 Bits 16 Bits 32 Bits 64 Bits (byte)42 (short)42 42 42L -128 bis 127 -32.768 bis 32.767 -2.147.483.648 bis 2.147.483.647 -9.223.372.036.854.775.808 bis 9.233.372.036.854.775.807 32 Bits 64 Bits 42.0F 42.0 -3.4*1038 bis 3.4*1038 -1.8*10308 bis 1.8*10308 16 Bits ‘A‘ Zeichen und Symbole true true, false • Dezimaltypen: float double • Zeichentyp: char • Logischer Typ: boolean Operatoren • Zuweisung: = Zuweisung • Vergleichsoperatoren für Zahlen und Zeichen: == != < > <= >= ist gleich ist ungleich ist kleiner als ist größer als ist kleiner oder gleich ist größer oder gleich • Logische Operatoren: && || ! und oder nicht equals ist gleich (bei Objekten) Kontrollfluss (1) • Bedingte Verzweigung: if (Bedingung) {Anweisung1; Anweisung2; ...} else {Anweisung1; Anweisung2; ...} switch (variable) { case wert_1: Anweisung1; break; case wert_2: Anweisung2; break; ... default: Defaultanweisung; break; } 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 {Anweisung1; Anweisung2;...} do // solange bis Bedingung {Anweisung1; Anweisung2;...} while (Bedingung) 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: 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 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 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 { public void run() { for ( int i=0; i<20; i++ ) { System.out.println( i ); } } } 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 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(); 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 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 Zustände von Threads • feststellbare Zustände: - nicht erzeugt - laufend (vom Scheduler berücksichtigt) - nicht laufend (vom Scheduler nicht berücksichtigt) - wartend - beendet Lebenszyklus von Java-Threads 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 ); } } 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!!! } } 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 { public void run() { synchronized(_fernseher) { while (!_fernseher.is_sport()) {} _fernseher.notify(); } } } 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 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 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 JavaProgrammen und nicht Java-Programmen herstellen. 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/ServerBeziehungen zwischen Objekten aufzubauen. • CORBA verfügbar u.a. in C, C++, Java, COBOL, Smalltalk, Ada, Lisp, Python. 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 Vor- und Nachteile • Vorteile 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 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. 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 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; } } RMIC 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 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 Der Server import import import import java.net.*; java.rmi.*; java.rmi.server.*; 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 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 );} } } 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. 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 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. 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 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 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 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();} } } Quellen • Vorlesungen P2 / P3 • Galileo- Openbook: http://www.galileocomputing.de/openbook/javainsel/ • Java-API: http://java.sun.com Weitere Quellen: • http://www.heise.de/ix/artikel/1998/11/166/ • http://www.thomasgraf.net/rmi/rmi2.htm • u.a. Fragen ?