account[accountNumber]

Werbung
Einführung
Willkommen zu Vorlesung + Praktikum
Parallele Prozesse
Einführung
zur Person:
•Harald Gläser
•Sprechstunde: Dienstag 12:45 C2.07
•[email protected]
•Webpage mit Lehrmaterial (bislang unvollständige Version):
http://www.informatik.hs-furtwangen.de/~glaeser/
Einführung
2 Leistungen um Modul zu bestehen (6 ECTS):
a) Klausur
b) Praktikum:
I. Alle Aufgaben müssen von jedem selbständig innerhalb
der Praktikumsstunden bearbeitet werden (kein “leeres
Blatt”, beim 2. Fehlen: ärztliches Attest).
II. Alle Aufgaben müssen bestanden werden.
III. Abgabe jeweils nächste Woche
IV. 4 Zeitstunden pro Praktikumstermin einplanen
Im Praktikum besteht Anwesenheitspflicht
Heute statt Praktikum Vorlesung in C1.20
- Erst mal „Stoff“ für Praktikum heranschaffen!
im email - UFO System anmelden !!
Link zu UFO:
http://www.hs-furtwangen.de/fachbereiche/in/deutsch/intranet/uf/?tg=0
Spale
Einführung
Klausurtermin: Mo 13.07.09 Uhrzeit 11:15 Raum: C0.09
Wie schreibe ich eine 1,0 in der Klausur?
•zu Hause Vorlesung vorbereiten:
•Folien im Netz / Buch von Oechsle vorher ausdrucken –
während Vorlesung Notizen in Folien machen
•während Vorlesung aufkommende Fragen sofort stellen!
•zu Hause Vorlesung nachbereiten:
•Alles verstanden ? – Sonst nächste Stunde Fragen stellen.
•4 Zeitstunden sind für die Vorlesung insgesamt vorgesehen ->
2,5 Zeitstunden für Vor- und Nachbereitung
-> 1,0 in der Klausur steht nichts im Wege ☺
Buch:
Parallele und verteilte Anwendung in Java
Rainer Oechsle
Hanser Verlag
puva.fh-trier.de
Weitere empfehlenswerte Quellen:
Java Tutorial, Firma Sun,
http://java.sun.com/docs/books/tutorial/essential/concurrency/
index.html
Java ist auch eine Insel, Christian Ullenboom
Fortgeschrittene Programmierung mit Java 5, Johannes Nowak
Parallelität, Nebenläufigkeit (concurrency)
echte Parallelität
Pseudoparallelität
Was ist Verteilung?
Vorgänge laufen auf lose gekoppelten Systemen ab
Lose gekoppelte Systeme: mehrere gekoppelte Prozessoren
ohne gemeinsamen Hauptspeicher
Programme, Prozesse, Threads
Prozess = Programm in Ausführung
Rechner
Prozess
Rechner
Prozess
Prozess
Objekt Objekt Objekt Objekt
Objekt
Objekt Objekt
Thread Thread Thread Thread
Thread
Thread
Prozess
Betriebssystem
Betriebssystem
•Programmstart (z.B. java HelloWorld) = neuer Prozess wird
gestartet
•Prozess besitzt eigenen Adressraum
•Jeder Prozess hat mindestens einen Thread
•Hintergrund Threads (z.B. garbage collection)
•main Thread -> kann neue Threads starten
•Threads desselben Prozesses können auf die gleichen Objekte
zugreifen
•Isolierung der Prozesse durch Systemaufrufe kontrolliert
durchbrechbar
•Adressraum eines Prozesses kann durch Systemaufruf vergrößert
werden oder Nachrichten über das Netz absetzen
Inhalt der Vorlesung
•Parallelität innerhalb eines Prozesses
•Verteilung (Parallelität zwischen Prozessen verschiedener Rechner)
•Client-Server Anwendungen
•webbasierte Anwendungen
•Socket
•RMI
•Parallelität zwischen Prozessen (desselben Rechners)
•Die Konzepte der Verteilung funktionieren auch für Prozesse,
die nur auf einem Rechner ablaufen
Java Threads
Ableiten von Klasse Thread
ODER
(wg. fehlender Mehrfachvererbung)
Ableiten von Interface Runnable
Ableiten von Klasse Thread
run:
run:Was
Wasim
imThread
Threadausgeführt
ausgeführtwird
wird
class
class MyThread
MyThread extends
extends Thread{
Thread{
public
void
run()
{
public void run() {
System.out.println(„Hello“);
System.out.println(„Hello“);
}}
public
public static
static void
void main(String[]
main(String[] args)
args) {{
MyThread
MyThread tt == new
new MyThread();
MyThread();
t.start();
t.start();
}}
}}
start:
start:startet
starteteinen
einenneuen,
neuen,parallelen
parallelenThread
Thread
•pro Thread Objekt darf start nur einmal aufgerufen werden
•Thread Objekt existiert unabhängig von Thread (also auch bevor
run ausgeführt wird bzw. nach Ende von run)
Was ist der Unterschied, wenn man statt start direkt run aufruft ?
direkter Aufruf von run
main
run
start Aufruf
main
run
Implementieren der Schnittstelle Runnable
public
public class
class SomethingToRun
SomethingToRun implements
implements Runnable
Runnable
{{
public
run
ist
durch
Runnable
vorgeschrieben
public void
void run()
run()
run
ist
durch
Runnable
vorgeschrieben
{{
System.out.println("Hello
System.out.println("Hello World");
World");
}}
public
public static
static void
void main(String[]
main(String[] args)
args)
{{
SomethingToRun
SomethingToRun runner
runner == new
new SomethingToRun();
SomethingToRun();
Thread
t
=
new
Thread(runner);
Thread t = new Thread(runner);
t.start();
t.start();
}}
}}
Thread
Threaderzeugen
erzeugendurch
durchKonstruktor
KonstruktorAufruf
Aufruf
Beispiel
public
public class
class Loop3
Loop3 extends
extends Thread
Thread {{
public
public Loop3(String
Loop3(String name)
name) {{
super(name);
super(name);
}}
public
public void
void run(){
run(){
for(int
i
for(int i == 1;
1; ii <=
<= 100;
100; i++){
i++){
System.out.println(getName()
System.out.println(getName() ++ "" ("
(" ++ ii ++ ")");
")");
try{
try{
sleep(100);
sleep(100);
}catch(InterruptedException
}catch(InterruptedException e){}
e){}
}}
}}
}}
public
public static
static void
void main(String[]
main(String[] args)
args) {{
Loop3
Loop3 t1
t1 == new
new Loop3("Thread
Loop3("Thread 1");
1");
Loop3
Loop3 t2
t2 == new
new Loop3("Thread
Loop3("Thread 2");
2");
Loop3
t3
=
new
Loop3("Thread
3");
Loop3 t3 = new Loop3("Thread 3");
t1.start();
t1.start();
t2.start();
t2.start();
t3.start();
t3.start();
}}
Code
Ausführung
•eventuell wachen mehrere Threads gleichzeitig zu einer
quantisierten Zeit auf
•Ausführungsreihenfolge kann nicht mit sleep erzwungen
werden
•Methodenlokale Variablen für jeden Thread vorhanden !
•Die Variablen werden bei einer Threadumschaltung „auf Eis gelegt“
•Wenn der Thread weitergeht, werden die Variablen weiterbenutzt
Probleme beim Zugriff auf gemeinsam genutzte Objekte
Andrea Müller
(Thread)
Petra Schmitt
(Thread)
Bank
Konto
0
47
public
public void
void transferMoney(int
transferMoney(int accountNumber,
accountNumber, float
float amount)
amount) {{
float
float oldAccount
oldAccount == account[accountNumber];
account[accountNumber];
account[accountNumber]
account[accountNumber] == oldAccount
oldAccount ++ amount;
amount;
}}
Probleme beim Zugriff auf gemeinsam genutzte Objekte
Andrea Müller
(Thread)
aktiv
Petra Schmitt
(Thread)
Bank
Konto
0
47
-100.0
public
accountNumber,
float
amount)
{{
public void
void transferMoney(int
transferMoney(int
accountNumber,
float
amount)
0
float
float oldAccount
oldAccount == account[accountNumber];
account[accountNumber];
STOP
}}
account[accountNumber]
account[accountNumber] == oldAccount
oldAccount ++ amount;
amount;
Probleme beim Zugriff auf gemeinsam genutzte Objekte
Andrea Müller
(Thread)
aktiv
Petra Schmitt
(Thread)
Bank
Konten
1000
47
-100.0
+1000.0
public
transferMoney(int
accountNumber,
float
amount)
{{
public void
void
transferMoney(int
accountNumber,
float
amount)
0 0
float
float oldAccount
oldAccount == account[accountNumber];
account[accountNumber];
+1000.0
0
+1000.0
account[accountNumber]
account[accountNumber] == oldAccount
oldAccount ++ amount;
amount;
}}
STOP
Probleme beim Zugriff auf gemeinsam genutzte Objekte
Andrea Müller
(Thread)
aktiv
Petra Schmitt
(Thread)
Bank
Konto
-100
47
-100.0
public
transferMoney(int
accountNumber,
float
amount)
{{
public void
void
transferMoney(int
accountNumber,
float
amount)
0
float
float oldAccount
oldAccount == account[accountNumber];
account[accountNumber];
-100.0
0
-100.0
account[accountNumber]
account[accountNumber] == oldAccount
oldAccount ++ amount;
amount;
}}
STOP
Warum löst dies nicht das Problem?
public
public void
void transferMoney(int
transferMoney(int accountNumber,
accountNumber, float
float amount)
amount) {{
account[accountNumber]
account[accountNumber] +=
+= amount;
amount;
}}
Diese Transaktion ist nicht atomar, da im Bytcode aus mehreren
Befehlen zusammengesetzt:
lade Inhalt von account[accountNumber] in ein Register;
addiere auf dieses Register den Inhalt von amount;
schreibe Register auf account[accountNumber] zurück;
Register übernimmt hier die Rolle von oldAccount!
Warum löst dies auch nicht das Problem?
class
class Bank
Bank {{
private
private boolean
boolean locked
locked == false;
false;
public
public void
void transferMoney(int
transferMoney(int accountNumber,
accountNumber, float
float amount)
amount) {{
while(locked);
while(locked);
locked
locked == true;
true;
float
float oldAccount
oldAccount == account[accountNumber];
account[accountNumber];
account[accountNumber]
account[accountNumber] == oldAccount
oldAccount ++ amount;
amount;
locked
=
false;
locked = false;
}}
}}
1. Aktives Warten (Schleife wird durchlaufen) ist schlecht (Polling)
2. Diese Transaktion ist auch nicht atomar:
Bytecode:
a) lade locked in ein Register;
b) falls dieses Register == true,
springe zurück zur vorigen Anweisung;
c) lade true in locked
Problem, wenn nach a) zum anderen Thread geschaltet wird, und der
gerade ein true reinschreibt!
Synchronized - Methoden
public
public synchronized
synchronized void
void transferMoney(int
transferMoney(int accountNumber,
accountNumber, float
float amount)
amount) {{
float
float oldAccount
oldAccount == account[accountNumber];
account[accountNumber];
account[accountNumber]
account[accountNumber] == oldAccount
oldAccount ++ amount;
amount;
}}
•Java hat Sperr-Mechanismus
•Sperre liegt bei Object -> alle Objekte erben Sperre
•Aufruf synchronized Methode = Sperre setzen auf zugehörigem
Objekt
•Wenn Methode abgearbeitet = Sperre frei -> nächster Thread kann
aufrufen
•Fremde Threads können keine synchronized Methoden dieses Objekts
aufrufen
•Sie erhalten keine CPU Zeit mehr (kein Polling)
•Erster Thread kann alle synchronized Methoden weiterhin benutzen
Synchronized - Blöcke
•Sperre der ganzen Bank ineffizient: Alle Konten gesperrt.
•Lösung: z.B. synchronized Block
public
public void
void transferMoney(int
transferMoney(int accountNumber,
accountNumber, float
float amount)
amount) {{
synchronized
synchronized (account[accountNumber])
(account[accountNumber]) {{
float
float oldAccount
oldAccount == account[accountNumber];
account[accountNumber];
account[accountNumber]
account[accountNumber] == oldAccount
oldAccount ++ amount;
amount;
}}
}}
•Objekt hinter synchronized gesperrt
•Nur benutztes Konto Objekt gesperrt
•synchronized Methode == synchronized(this) Block
•über das Block Objekt lassen sich mehrere synchronized Blöcke an
unterschiedlichen Stellen im Programm gleichzeitig sperren
Wirkung von synchronized
class
class CC {{
public
public void
void m1()
m1() {...}
{...}
public
void
m2()
{...}
public void m2() {...}
public
public synchronized
synchronized void
void ms1()
ms1() {...}
{...}
public
synchronized
void
ms2()
{...}
public synchronized void ms2() {...}
}}
C o1 = new C();
C o2 = new C();
Thread B:
o1.m1();
o1.m2()
o1.ms1();
o1.ms2()
o2.m1();
o2.m2()
o2.ms1();
o2.ms2()
Thread A:
o1.ms1()
geht
geht nicht
(o1 gesperrt)
geht
geht
(o2 anderes
Objekt)
Thread A:
o1.m1()
geht
geht
geht
geht
Thread A ruft zuerst auf, dann versucht B aufzurufen
static synchronized Methode:
•Sperre gilt für die Klasse bzw. die static synchronized Methoden der
Klasse
•Objekte der Klasse werden durch static synchronized nicht gesperrt
Ein gesperrtes Objekt kann man auch „Monitor“ nennen,
nach einem allgemeinen Konzept von Hoare (1974)
Klemmt das ?
class
class MyClass
MyClass {{
public
public synchronized
synchronized void
void m1()
m1() {{
m2();
m2();
}}
public
public synchronized
synchronized void
void m2()
m2() {}
{}
public
static
void
main(String[]
public static void main(String[] args)
args) {{
MyClass
MyClass obj
obj == new
new MyClass();
MyClass();
obj.m1();
obj.m1();
//wird
//wird diese
diese Stelle
Stelle erreicht??
erreicht??
}}
}}
Nein, klemmt nicht. Der eigene Thread wird nicht blockiert.
Ist Synchronisation von Zugriff auf methodenlokale Variablen nötig?
•primitive Datentypen:
nein, da für jede Methode eigene Variablen da
•Referenz Datentypen:
nein, wenn in Methode Objekt erzeugt
möglicherweise, wenn auf Attribute verwiesen wird ->
könnten von anderen Threads verändert werden
Ist Synchronisation von Zugriff auf Attribute mit primitiven Datentyp
nötig?
•Lesen und Schreiben aller Referenzen und primitiver Datentypen
außer double und long in Java atomar
ABER:
Optimierung vom Compiler im Bytecode möglich: Wert nicht immer
vom Hauptspeicher lesen sondern in Register zwischenspeichern ->
synchronized verhindert dass! (oder Attribut mit volatile modifizieren)
•long, double haben 64bit -> Lesen, Schreiben erfolgt in 32bit Schritten
nicht atomar -> synchronized
Regel für synchronized
Wenn zuerst Zustands ändernde Threads und dann nur noch
lesende Threads auf Attribut zugreifen -> kein synchronized
(Neustarten von Threads hat volatile Effekt).
Wenn mindestens ein Zustands ändernder Thread gleichzeitig mit
anderen Threads auf ein Objekt zugreift -> synchronized von allen
lesenden und schreibenden Methoden
•Schreibende Methoden überführen Objekt von einem
konsistenten Zustand in einen anderen in MEHREREN Schritten
•Wenn in Zwischenschritt etwas gelesen wird ist das undefiniert
•Beispiel: interne Überweisungen in Bank und Kontrollroutine,
die prüft, ob Summe der Konten immer gleich ist
•Kontrollroutine kann lesen, wenn gerade ein Betrag abgebucht
aber noch nicht wieder auf dem anderen Konto gelandet ist
-> lesende Methoden müssen auch gesperrt
-> FOLGE ??
werden
Wie kann man auf das Ende eines Thread warten?
MyThread
MyThread tt == new
new MyThread();
MyThread();
t.start();
t.start();
while
while (t.isAlive());
(t.isAlive());
//
jetzt
// jetzt gilt:
gilt: t.isAlive()
t.isAlive() ==
== false;
false; also
also ist
ist der
der Thread
Thread zuende
zuende
Was ist daran schlecht ?
Polling
MyThread
MyThread tt == new
new MyThread();
MyThread();
t.start();
t.start();
t.join();
t.join();
//
// der
der Thread
Thread tt ist
ist zuende
zuende
Beenden von Threads
•Methode stop bricht Thread sofort ab
•stop ist deprecated – Warum ?
•stop bringt Objekte eventuell vorübergehend in einen
inkonsistenten Zustand (Beispiel, das Bankenproblem mit den
internen Konten)
•nur der Programmierer kann Thread konsistent zum Ende führen
•Lösung: interrupt Flag bei dem Thread Objekt benutzen
public
public void
void run()
run() {{
int
interrupted
Flag
wird
untersucht
int ii == 0;
0;
interrupted
Flag
wird
untersucht
try
{
try {
while(!isInterrupted())
while(!isInterrupted()) {{
i++;
i++;
System.out.println("Hello
System.out.println("Hello World
World ("
(" ++ ii ++ ")");
")");
Thread.sleep(3000);
Thread.sleep(3000);
}}
Unterbrechung
Unterbrechungauch
auchwährend
währendsleep,
sleep,join,
join,wait!
wait!
}}
catch(InterruptedException
e){
Dann
catch(InterruptedException
e){eine
Dannerfolgt
erfolgt
eineInterrupt
InterruptException
Exception
System.out.println(e.getMessage());
System.out.println(e.getMessage());
}}
System.out.println("thread
System.out.println("thread terminating
terminating ...");
...");
}}
catch
setzt interrupted Flag wieder auf false
catch setzt interrupted Flag wieder auf false
}}
public
public static
static void
void main(String[]
main(String[] args){
args){
StopThreadByInterrupt
st
=
new
StopThreadByInterrupt st = new StopThreadByInterrupt();
StopThreadByInterrupt();
try{
try{
Thread.sleep(9100);
Thread.sleep(9100);
}}
catch(InterruptedException
catch(InterruptedException e){}
e){}
st.interrupt();
st.interrupt();
}}
Thread
Threadsoll
sollabgebrochen
abgebrochenwerden
werden
static
staticboolean
booleaninterrupted():
interrupted():frägt
frägtFlag
Flagab,
ab,und
undsetzt
setztes
esauf
auffalse!
false!
Asynchrone Beauftragung mit befristetem Warten
private
private static
static final
final int
int TIMEOUT=10000;
TIMEOUT=10000;
public
public static
static void
void main(String[]
main(String[] args)
args) {{
Server
Server server
server == new
new Server(...);
Server(...);
server.start();
server.start();
//auf
//auf Serverende
Serverende beschränkte
beschränkte Zeit
Zeit warten
warten
try
try {{
Maximale
Wartezeit
auf
den
Thread
Maximale
Wartezeit
auf
den
Thread
server.join(TIMEOUT);
server.join(TIMEOUT);
}} catch(InterruptedException
catch(InterruptedException e)
e) {}
{}
//Server
//Server sanft
sanft abbrechen
abbrechen
server.interrupt();
server.interrupt();
//Synchronisation
//Synchronisation mit
mit Server
Server
try
{
try {
server.join();
server.join();
}} catch(InterruptedExceptione)
catch(InterruptedExceptione) {}
{}
//Ergebnis
//Ergebnis abholen
abholen und
und ausgeben
ausgeben
double
result
=
server.getResult();
double result = server.getResult();
System.out.println("Ergebnis:"+result);
System.out.println("Ergebnis:"+result);
}}
Vorläufige Zusammenfassung:
•Objekte kapseln Zustände
•Für Zustände gelten Konsistenzbedingungen (Invarianten)
•Methoden überführen von einem konsistenten Zustand in einen
anderen, wobei zwischendurch die Konsistenz evtl. verletzt wird
•Synchronized Methoden gewährleisten, dass Threads bei
Methodenaufruf konsistenten Zustand vorfinden
•Synchronized sperrt Objekte in inkonsistenten Zuständen vor
Zugriffen von anderen Threads
•Manchmal sollen Objekte noch weiter gesperrt sein, bis weitere
anwendungsabhängige Bedingungen erfüllt sind
Beispiel Parkhaus
•Jedes Auto ein Thread
•Ein Parkhausobjekt mit Methoden enter und leave
class
class ParkingGarage
ParkingGarage {{
private
private int
int places;
places;
public
public ParkingGarage(int
ParkingGarage(int places)
places) {{
if(places
if(places << 0)
0) places
places == 0;
0;
this.places
=
places;
this.places = places;
}}
public
public synchronized
synchronized void
void enter()
enter() {{
while(places
while(places ==
== 0);
0);
places--;
places--;
}}
public
public synchronized
synchronized void
void leave()
leave() {{
places++;
places++;
}}
}}
Was
Wasist
isthier
hierfalsch
falsch??
•Polling
•Dauersperre, wenn
places == 0
nicht in synchronized Methode warten!
private
private synchronized
synchronized boolean
boolean isFull()
isFull() {{
return
return (places
(places ==
== 0);
0);
}}
private
private synchronized
synchronized void
void decrement()
decrement() {{
places--;
places--;
}}
public
public void
void enter()
enter() {{
while(isFull());
while(isFull());
decrement();
decrement();
}}
Was
Wasist
isthier
hierfalsch
falsch??
public
public synchronized
synchronized void
void leave()
leave() {{
places++;
places++;
}}
Was kann passieren, wenn noch genau ein Platz im Parkhaus frei
ist und zwei Autos reinfahren wollen?
Zustandsübergangsdiagramm für Threads
Lösung: wait und notify
class
class Object
Object {{
……
public
public final
final void
void wait()
wait() throws
throws InterruptedException
InterruptedException {…}
{…}
public
final
void
notify()
{…}
public final void notify() {…}
}}
•beide Methoden dürfen nur auf gesperrten Objekten (in
synchronized Methoden) aufgerufen werden sonst
IllegalMonitorStateException
•wait schiebt Thread in Warteschlange mit Threads und hebt
Sperre auf
•notify erlaubt irgendeinem (unbestimmbaren) Thread aus der
Warteschlange weiterzumachen (da, wo er aufgehört hatte)
•ist Warteschlange leer, bewirkt notify nichts
Lösung des Parkhausproblems:
public
public synchronized
synchronized void
void enter()
enter() {{
while(places
while(places ==
== 0)
0) {{
try
try {{
wait();
wait();
}} catch
catch (InterruptedException
(InterruptedException e)
e) {{ }}
}}
places--;
places--;
}}
public
public synchronized
synchronized void
void leave()
leave() {{
places++;
places++;
notify();
notify();
}}
Ist das Programm mit folgender Änderung noch korrekt?
public
public synchronized
synchronized void
void leave()
leave() {{
notify();
notify();
places++;
places++;
}}
Ja, das Programm ist noch korrekt.
Der durch notify geweckte Thread sperrt das Objekt wieder, -aber erst sobald er kann, d.h. wenn der „notify“ Thread die
Methode verlassen hat
Ist das Programm mit folgender Änderung noch korrekt?
public
public synchronized
synchronized void
void enter()
enter() {{
if
if (places
(places ==
== 0)
0) {{ //
// if
if statt
statt while!
while!
try
try {{
wait();
wait();
}} catch
catch (InterruptedException
(InterruptedException e)
e) {{ }}
}}
places--;
places--;
}}
Nein, das Programm ist nicht korrekt.
Der gerade geweckte Thread könnte sofort wieder gestoppt
werden, indem auf einen anderen Thread umgeschaltet wird, der
gerade enter aufruft – was passiert dann ?
Ein entblockierter Thread muss die Bedingung, auf die er wartet,
erneut prüfen - sie könnte noch nicht gültig sein oder schon durch
schnellere Threads wieder invalidiert worden sein.
Unterschiede sleep und wait ?
sleep
Methoden der Klasse
Thread
Klassenmethoden
Keine Bedingungen für
Aufruf
wait
Methoden der Klasse
Object
Instanzmethoden
Kann nur auf Objekt
aufgerufen werden, das
gesperrt ist
Falls Objekte gesperrt sind, Die Sperre des Objekts, auf
bleiben sie gesperrt
das wait aufgerufen wird,
wird aufgehoben
(eventuelle andere Sperren
bleiben gesetzt)
Das Erzeuger Verbraucher Problem
Producer
Buffer
data
available = true/false
Consumer
private
private boolean
boolean available
available == false;
false;
private
int
data;
Die Klasse Buffer
private int data;
public
public synchronized
synchronized void
void put(int
put(int x)
x) {{
while(available)
while(available) {{
try
try {{
wait();
wait();
}}
catch(InterruptedException
catch(InterruptedException e)
e) {}
{}
}}
data
data == x;
x;
available
•es wird ein int Wert gespeichert
available == true;
true;
notify();
notify();
•available zeigt an, ob ein neuer
}}
public
public synchronized
synchronized int
int get()
get() {{ Wert im Buffer vorliegt
while(!available){
while(!available){
try{
try{
wait();
wait();
}}
catch(InterruptedException
catch(InterruptedException e)
e) {}
{}
}}
available
available == false;
false;
notify();
notify();
Das Programm stoppt „willkürlich“!
return
data;
return data;
}}
Threadzustände im Verlauf der Zeit
data
Consumer
Consumerc1
c1c2
c2
Producer
Producerp1
p1
put
get ready
wait Buffer
c1
leer
p1
notify()
c2
voll
Threadumschaltung
leer
Stillstand
Stillstand
Würde das Programm mit nur einem Producer und Consumer Objekt
funktionieren?
Wenn c1 statt c2 p2 geweckt hätte – was wäre dann gewesen?
Der falsche Thread wurde geweckt
Lösung: notifyAll
notifyAll()
•weckt alle Threads in Warteschlange des Objekts
•Objekt muss gesperrt sein bei Aufruf von notifyAll()
•wenn Warteschlange leer ist notifyAll wirkungslos
•da alle Threads nach Aufwecken die Wartebedingung (nochmals)
überprüfen, kann jeder notify durch notifyAll ersetzt werden (aber
nicht umgekehrt)
•notifyAll ist weniger effizient als notify
Wann notifyAll statt notify?
1. Threads mit unterschiedlichen Wartebedingungen in
Warteschlange (z.B. „Buffer ist voll“ und „Buffer ist leer“) ->
Gefahr: falscher Thread wird geweckt
2. Durch Änderung Objektzustand können mehrere Threads
weiterlaufen (z.B. mehrer Autos an grüner Ampel)
Prioritäten von Threads
public
public class
class Thread
Thread {{
……
public
public final
final void
void setPriority(int
setPriority(int newPriority)
newPriority) {…}
{…}
public
final
in
getPriority()
{…}
public final in getPriority() {…}
……
}}
Werte der Priority (je höher desto größere Priorität) (Konstanten in
Thread)
1 == MIN_PRIORITY
5 == NORM_PRIORITY
10 == MAX_PRIORITY
Priorität entscheidet über die Prozessorzuteilungsstrategie
(Scheduling):
•Arbeitsdauer eines Thread bis zur nächsten Umschaltung
•auf welchen Thread als nächstes umgeschaltet wird
•Auswirkung der Priorität auf das Scheduling ist vom Betriebssystem
abhängig und nicht vorgeschrieben
•z.B. können die Prioritäten 1 und 2 im Betriebssystem die gleiche
Priorität bedeuten
•z.B. kann die Arbeitsdauer eines Thread bis zur nächsten
Umschaltung für alle Prioritäten gleich sein etc.
•Ein / Ausgabe Threads sollten hohe Priorität haben
•rechenintensive Threads sollten niedrige Priorität haben
1. Korrektheit von Programm darf nicht von Prioritäten z.B.
Umschaltverhalten des Systems abhängen (nie Prioritäten für
Synchronisation benutzen)
2. Prioritäten können Effizienz eines Systems verbessern
Mit yield kann man auf einen anderen Thread umschalten
(sollte aber in produktivem Code nicht verwendet werden – warum ?)
Daemon- und User Threads
•Ein Thread kann als Daemon oder User Thread gesetzt werden
•Diese Threads unterscheiden sich nur bezüglich des Endes eines
Java Prozesses
•Ein Java Prozess ist beendet, wenn alle User Threads beendet sind
•Daemon Threads sind dann auch zuende, selbst wenn sie
Endlosschleifen sind
•Berühmte Daemon Threads sind
•Reference Handler – sucht nach nicht mehr referenzierten
Objekten zur Speicherfreigabe
•Finalizer – ruft die Methode finalize von jedem Objekt auf, dass
nicht mehr referenziert wird
•Beide Threads machen „garbage collection“
•Jeder Thread (bis auf main Thread) kann in Daemon Thread
verwandelt werden, aber nur bevor er gestartet wurde
Frage: wenn man Java Prozess abschiesst – laufen Daemons noch weiter
public
public class
class Thread
Thread {{
……
public
public final
final void
void setDaemon(boolean
setDaemon(boolean on)
on) {…}
{…}
public
final
boolean
isDaemon()
{…}
public final boolean isDaemon() {…}
……
}}
Priorität eines Thread ist unabhängig davon, ob er Daemon oder
User Thread ist
Zustandsübergangsdiagramm für Threads
•getState – Statusabfrage eines Thread (erzeugt, laufend, blockiert,
beendet usw.
•getState unterscheidet READY und RUNNING nicht – beides ist
RUNNABLE!
Aktive und passive Klassen
•Thread Anwendungen haben aktive und passive Klassen
•Aktive Klassen sind die Thread bzw. Runnable Klassen
•Die Objekte von passive Klassen werden von den Threads benutzt
•Synchronisation (synchronized, wait, notify, notifyAll wird von den
passiven Klassen gemacht)
Beispiele:
Aktive Klassen
Bankangestellte
Autos
Erzeuger und Verbraucher
Passive Klassen
Bank und Konten
Parkhaus
Puffer
Synchronisationskonzepte aus UNIX
•UNIX Synchronisationskonzepte für Prozess ohne gemeinsamen
Adressraum
•aber auch für Synchronisation innerhalb eines (Java-) Prozesses
interessant
Semaphor
•Das Parkhaus im Parkhausbeispiel ist ein Semaphor
•Übliche Bezeichner:
Semaphore
== ParkingGarage
value
== places
p (passeeren)
== enter
v (vrijgeven)
== leave
•Im Unterschied zur Parkhaus Analogie macht auch ein Semaphor
mit value = 0 Sinn!
Value 0 bedeutet: Semaphor ist von vornherein gesperrt
Methode v kann hier den Semaphor öffnen (value++)
Value 1 bedeutet: Semaphor ist von vornherein erst mal offen
Methode p sperrt hier den Semaphor (value--)
Mutex Semaphore
•Mutex == mutual exclusion == gegenseitiger Ausschluss
•z.B. synchronized Methode bewirkt gegenseitigen Ausschluss
•Bei unterschiedlichen Prozessen oder anderen Programmiersprachen
gibt’s kein synchronized
•Beispiel in Java – lässt sich aber allgemein übertragen
class
class MutexThread
MutexThread extends
extends Thread
Thread {{
private
private Semaphore
Semaphore mutex;
mutex;
public
{{
public MutexThread(Semaphore
MutexThread(Semaphore mutex)
mutex)
this.mutex
this.mutex == mutex;
mutex;
start();
start();
}}
public
public void
void run()
run() {{
while(true)
while(true) {{
mutex.p();
mutex.p();
System.out.println("kritischen
System.out.println("kritischen Abschnitt
Abschnitt betreten");
betreten");
try
try {{
//
// kritischer
kritischer Zugriff
Zugriff auf
auf gemeinsame
gemeinsame Daten
Daten oder
oder so
so
}} catch(InterruptedException
e){}
catch(InterruptedException e){}
System.out.println("kritischer
System.out.println("kritischer Abschnitt
Abschnitt wird
wird verlassen");
verlassen");
mutex.v();
mutex.v();
}}
}}
}}
•Viele
•VieleMutex
MutexThreads
Threadsgreifen
greifenauf
aufdasselbe
dasselbeSemaphor
SemaphorObjekt
Objektzu
zu
•Semaphor
•Semaphorwird
wirdmit
miteinem
einemPlatz
Platzerzeugt!
erzeugt!Maximal
Maximalein
einThread
Thread
kann
kannkritischen
kritischenAbschnitt
Abschnittbetreten
betreten
Unterschiede bei UNIX
•Gemeinsamer Zugriff auf ein Semaphor Objekt nicht möglich
•Semaphor existiert im Betriebssystemkern
•Wird durch Kennung (Parameter des des Systemaufrufs)
angesprochen
•Zugriff auf gemeinsame Daten von Threads unterschiedlicher
Prozesse möglich über die Nutzung von gemeinsamen Speicher
•Gemeinsamer Speicher wird durch Systemaufrufe eingerichtet
siehe auch Herold: Linux/Unix Systemprogrammierung
Einfache Semaphore zur Herstellung vorgegebener
Ausführungsreihenfolgen
Erst soll Thread t1 dann entweder Thread t2, t3 oder t4 (oder alle
gleichzeitig) und zum Schluss Thread t5 ausgeführt werden
t2
t1
t3
t4
Für jeden Pfeil wird ein
Semaphor verwendet
t5
Semaphore wirken hier wie
Durchlassschranken, die
zunächst immer zu sind
(value ==0)
•Für jeden Pfeil wird ein Semaphor mit Anfangswert 0 verwendet
•Alle Threads (t1 bis t5) werden gestartet
•t2 bis t5 rufen auf allen jeweils einkommenden Semaphoren p auf
und verschwinden daher in Wartezuständen (value == 0)
•Nur t1 arbeitet seine „Nutzlast“ an Befehlen ab
•Dann ruft t1 auf den einkommenden Semaphoren von t2 bis t4 v auf
•t2 bis t4 werden per notify geweckt und in willkürlicher
Reihenfolge abgearbeitet
•t2 bis t4 rufen auf ihren ausgehenden Semaphoren nach dem
Abarbeiten v auf.
•Wenn t5 durch alle seine einkommenden Semaphore per notify
durchgeschleust wurde, kann er abgearbeitet werden
(v und p sind Methoden der Klasse Semaphore)
Additive Semaphore
•Semaphor kann nicht nur um 1
sondern um beliebige Werte
erhöht oder erniedrigt werden
•Neue Methode change(int x) (x
negativ oder positiv)
•Neue Methoden p(int x), v(int x)
(x >= 0)
•notifyAll statt notify bei
Methode v, weil im Falle
additiver Semaphore mehrere
Threads weiterlaufen können (die
z.B. den value jeweils nur um 1
erniedrigen, der value steht
jedoch auf z.B. 5)
public
public void
void change(int
change(int x)
x) {{
if
if (x
(x >> 0)
0) {{
v(x);
v(x);
}} else
else if
if (x
(x << 0)
0) {{
p(-x);
p(-x);
}}
}}
public
public synchronized
synchronized void
void v(int
v(int x)
x) {{
if
if (x
(x <=
<= 0)
0) {{
return;
return;
}}
value
value +=
+= x;
x;
notifyAll();
notifyAll(); //
// nicht
nicht notify!
notify!
}}
•Beispiel (Leser/Schreiber Problem) kommt später
Es macht Unterschied, ob value auf einen Schlag um 3 erniedrigt
wird oder 3 mal um 1
11mal
33mal
mal-3
-3
mal-1
-1
p(x)
value
Warteschlange
4
3
p(x)
value
Warteschlange
4
1
2
1
0
1
0
Verklemmung
Verklemmung
keine
keineVerklemmung
Verklemmung
Semaphorgruppen
•Verallgemeinerung der additiven Semaphore (Veränderung von
value um mehr als 1)
•Änderung aller Semaphoren der Gruppe gleichzeitig um jeweils
einen eigenen Wert x
•Es wird keine Änderung durchgeführt, wenn auch nur bei einer
Semaphore ein negativer value entstünde
•Der Versuch einer solchen Änderung führt in Warteschlange
Semaphorengruppe
-3 -2 1
4
5
1
1
3
2
Semaphorengruppe
-3 2 -3
4
5
1
4
5
1
Warteschlange
Warteschlange
•Anwendungsbeispiel: Philosophenproblem, siehe später
public
public synchronized
synchronized void
void changeValues(int[]
changeValues(int[] deltas){
deltas){
if(deltas.length
if(deltas.length !=
!= values.length)
values.length)
return;
return;
while(!canChange(deltas))
while(!canChange(deltas)) {{
try
try {{
wait();
wait();
}} catch(InterruptedException
catch(InterruptedException e)
e) {}
{}
}}
doChange(deltas);
doChange(deltas);
notifyAll();
notifyAll();
}}
Warum notifyAll statt notify?
1. Die durchgeführten Änderungen können mehreren Threads
Weiterlaufen ermöglichen (siehe additive Threads)
2. Der „falsche“ Thread könnte geweckt werden
Warteschlange
Semaphorengruppe
3
0
1
3
0
1
-4 4 1
-4 -2 1
Falscher
FalscherThread
Threadgeweckt
geweckt
040
3
4
1
3
4
1
notify()
2. Der „falsche“ Thread könnte geweckt werden
Warteschlange
Semaphorengruppe
3
0
1
3
0
1
-4 4 1
4 -2 1
3
040
Bei
BeiSemaphorengruppen
Semaphorengruppenund
und
additiven
additivenSemaphoren
Semaphorenkann
kannes
es
unterschiedliche
unterschiedliche
Wartebedingungen
Wartebedingungengeben:
geben:der
der
erste
ersteThread
Threadwartet
wartetauf
auf
1.
1.Semaphore
Semaphore-4
-4möglich
möglich
der
derzweite
zweiteThread
Threadwartet
wartetauf
auf
2.
2.Semaphore
Semaphore-2
-2möglich
möglich
0
1
Richtiger
RichtigerThread
Threadgeweckt
geweckt
3
4
1
7
2
2
N Buffer
Verallgemeinerung des Erzeuger-Verbraucher Problems auf
mehrere Speicherplätze:
private
private int
int head;
head;
private
int
tail;
private int tail;
private
private int
int numberOfElements;
numberOfElements;
private
int[]
private int[] data;
data;
Am tail wird eingefügt, am
head herausgenommen
Message Queue
•Ein Sender sendet Nachrichten zu einem Empfänger. Dazu werden sie
in einer MessageQueue zwischengespeichert
•Der Sender ruft send auf um die Nachricht in die Queue einzureihen
•Der Empfänger ruft receive auf, um ein Nachricht aus der Queue zu
holen
•Nachrichten = byte Felder unterschiedlicher Länge
•MessageQueue = NBuffer mit einem Feld von byte Feldern
(Nachrichten)
•Methodennamen: put = send(byte[] msg); get = byte[] receive()
•Damit Sender Nachricht nicht manipulieren kann, wenn sie in der
Queue ist, wird Kopie gespeichert
Konstruktor:
public
public MessageQueue(int
MessageQueue(int capacity)
capacity) {{
……
msgQueue
msgQueue == new
new byte[capacity][];
byte[capacity][];
}}
Pipe
•MessageQueue ist nachrichtenorientiert, Pipe ist datenstromorientiert
•Datenstrom = Grenzen zwischen den byte-Folgen sind aufgehoben –
ein einziger byte Strom
•-> eindimensionales byte –Feld in NBuffer genügt
Pipe, so wie sie bei Oechsle implementiert ist:
•Senden = unteilbare Aktion. Wenn Restplatz im Puffer kleiner als
Länge der zusendenden Daten -> wait
•Wenn Länge der zusendenden Daten > Puffergröße, wird Nachricht
geteilt gesendet -> aber Nachrichten können dadurch „durchmischt“
werden !?!?!
(nicht bei Oechsle sondern von Java ist auch eine Insel)
Java Threads können über Pipes kommunizieren
Thread B
Thread A
write(int b)
connect
PipedOutputStream
PipedWriter
PipedInputStream
PipedReader
int read()
ein Byte (0 bis 255) oder -1
•Wenn Ringspeicher in PipedInput voll, dann wartet write(int c)
•Größe Ringspeicher Standardmäßig 1024 – seit Java 6 einstellbar
Schreiben „auf einen Schlag“:
public void write(byte[] b, int off, int len)
Writes len bytes from the specified byte
array starting at offset off to this piped
output stream. This method blocks until all
the bytes are written to the output stream.
Was, wenn len > 1024?
Nichts – denn bytes werden sukzessive rausgeschrieben
class PipedOutputStream extends OutputStream {
private PipedInputStream sink;
public PipedOutputStream( PipedInputStream snk )
throws IOException {
/* Auskommentierte Fehlerbehandlung */
sink = snk;
tail
snk.in = –1;
snk.out = 0;
head
snk.connected = true;
}
public void write( int b ) throws IOException {
if ( sink == null )
throw new IOException( "Pipe not connected" );
sink.receive( b );
}
}
Receives a byte of data. This method will block if
no input is available.
public int read(byte[] b, int off, int len)
Reads up to len bytes of data from this piped input stream into
an array of bytes. Less than len bytes will be read if the end of
the data stream is reached or if len exceeds the pipe's buffer
size. the method blocks until at least 1 byte of input is available
or end of the stream has been detected
Parameters:
b - the buffer into which the data is read.
off - the start offset in the destination array b
len - the maximum number of bytes read.
Returns:
the total number of bytes read into the buffer, or -1 if
there is no more data because the end of the stream has
been reached.
Philosophen Problem
•Philosophen essen und denken abwechselnd
•Ein Philosoph braucht die zwei Gabeln neben seinem Teller um zu
essen
•Nicht alle Philosophen können gleichzeitig essen
Java Lösung mit synchronized, wait, notifyAll
public
public synchronized
synchronized void
void takeFork(int
takeFork(int number)
number) {{
while(forkUsed[left(number)]
while(forkUsed[left(number)] ||
|| forkUsed[right(number)]){
forkUsed[right(number)]){
try
{
try {
wait();
wait();
}}
catch(InterruptedException
catch(InterruptedException e){}
e){}
}}
forkUsed[left(number)]
Nummer
des
Philosophen
forkUsed[left(number)] == true;
true;
Nummer
des
Philosophen
forkUsed[right(number)]
=
true;
forkUsed[right(number)] = true;
}}
public
public synchronized
synchronized void
void putFork(int
putFork(int number)
number) {{
forkUsed[left(number)]
forkUsed[left(number)] == false;
false;
forkUsed[right(number)]
=
forkUsed[right(number)] = false;
false;
notifyAll();
notifyAll();
}}
}}
Jeder Philosoph ein Thread mit
while in run() Methode:
while(true){
while(true){
think(number);
think(number);
table.takeFork(number);
table.takeFork(number);
eat(number);
eat(number);
table.putFork(number);
table.putFork(number);
}}
Naive Lösung mit Semaphoren
•Lösung, die außer Semaphoren keine Synchronisationskonzepte von
Java einsetzt
•Konzeptionell wie Unix
•Jede Gabel = Sempahor mit value = 1
•Vor Essen p Methode auf rechter und linker Gabel
•Nach Essen v Methode auf rechter und linker Gabel
while(true){
while(true){
think(number);
think(number);
sems[left].p;
sems[left].p;
sems[right].p;
sems[right].p;
eat(number);
eat(number);
sems[left].p;
sems[left].p;
sems[right].p;
sems[right].p;
}}
eine
einelinke
linkeGabel
Gabel
Diese Lösung kann zu Verklemmung führen – warum?
alle Philosophen nehmen gleichzeitig die linke Gabel!
Lösung mit Semaphorgruppen
• Jede Gabel = ein Semaphor
run
runMethode
Methodeeines
einesPhilosopen
PhilosopenThread
Thread
public
public void
void run(){
run(){
int[]
deltas
int[] deltas == new
new int[sems.getNumberOfMembers()];
int[sems.getNumberOfMembers()];
for(int
for(int ii == 0;
0; ii << deltas.length;
deltas.length; i++)
i++)
deltas[i]
deltas[i] == 0;
0;
int
number
=
leftFork;
int number = leftFork;
Semaphorengruppe
Semaphorengruppe
while(true)
{
while(true) {
mit
think(number);
mitEinsen
Einsen
think(number);
deltas[leftFork]
deltas[leftFork] == -1;
-1;
initialisiert
initialisiert
deltas[rightFork]
=
-1;
deltas[rightFork] = -1;
sems.changeValues(deltas);
sems.changeValues(deltas);
eat(number);
eat(number);
deltas[leftFork]
deltas[leftFork] == 1;
1;
deltas[rightFork]
deltas[rightFork] == 1;
1;
sems.changeValues(deltas);
sems.changeValues(deltas);
}}
}}
•Alle Philosophen nehmen linke Gabel noch möglich?
Nein, da immer zwei Gabeln von einem Philosophen auf einen
Schlag genommen werden!
Leser – Schreiber Problem
•Viele Threads greifen lesend oder schreibend auf Datensatz zu
•Alle Threads können nur entweder lesen oder schreiben
•Lösung: gegenseitiger Ausschluss:
Keine Konsistenzprobleme– aber unnötige Einschränkung der
Parallelität, denn alle Leser können gleichzeitig zugreifen
•Nur ein Schreiber zu einer Zeit möglich
Prioritätenproblem:
•Leser Priorität: kein Leser greift zu -> schreiben erlaubt
(Aktualität der Daten unwichtig)
•Schreiber Priorität: kein Schreiber greift zu -> lesen erlaubt
(Aktualität der Daten wichtig)
•Nicht priorisierter Thread muss auch irgendwann mal
drankommen können
•Warteliste ermöglicht das in jedem Fall
Lösung mit synchronized-wait-notifyAll
•Eine Klasse AccessControl –hier sind die Daten, auf die dann
Leser/Schreiber Threads zugreifen
•Zähler für wartende und aktive Threads
public
public Object
Object read(){
read(){
beforeRead();
beforeRead();
Object
Object obj
obj == reallyRead();
reallyRead();
afterRead();
afterRead();
return
return obj;
obj;
}}
private
private synchronized
synchronized void
void afterRead(){
afterRead(){
activeReaders--;
activeReaders--;
notifyAll();
notifyAll();
}}
private
private synchronized
synchronized void
void beforeRead(){
beforeRead(){
waitingReaders++;
waitingReaders++;
while(waitingWriters
while(waitingWriters !=
!= 00 ||
|| activeWriters
activeWriters !=
!= 0){
0){
try
{
try {
wait();
wait();
bevorzugt
}} catch(InterruptedException
bevorzugtSchreiber
Schreiber
catch(InterruptedException e)
e) {}
{}
}}
waitingReaders--;
waitingReaders--;
activeReaders++;
activeReaders++;
}}
Schreiber
SchreiberMethoden
Methodenanalog
analog-- wie
wiemuss
mussaber
aberdort
dortdie
die
Wartebedingung
Wartebedingungaussehen?
aussehen?
Lösung mit additiven Semaphoren
•Einfaches Programm ergibt Bevorzugung Leser
•Bevorzugung Schreiber auch möglich, aber anderer Ansatz
•read Methode wie vorige Folie
private
private void
void beforeWrite(){
beforeWrite(){
sem.p(MAX);
sem.p(MAX);
}}
private
private void
void afterWrite(){
afterWrite(){
sem.v(MAX);
sem.v(MAX);
}}
private
private void
void beforeRead(){
beforeRead(){
sem.p(1);
sem.p(1);
}}
private
private void
void afterRead(){
afterRead(){
sem.v(1);
sem.v(1);
}}
class
class AccessControlSem
AccessControlSem
private
private static
static final
final int
int MAX
MAX == 1000;
1000;
private
AdditiveSemaphore
sem
=
private AdditiveSemaphore sem = new
new AdditiveSemaphore(MAX);
AdditiveSemaphore(MAX);
……
}}
MAX
MAX==maximale
maximale
Anzahl
Anzahlgleichzeitiger
gleichzeitiger
Leser
Leser
Schablonen zur Nutzung der Synchronisationsprimitive
ok
Invariante
ok
verletzt
Methode
Zustand A
Zustand B
synchronized
•Mehrere Threads greifen auf Attribute zu
•>= 1 Thread ist schreibend
->
lesende oder schreibende Methoden synchronized
Bedingung für Zustandsänderung
(z.B. get von Wert aus Puffer nur, wenn einer da ist)
Zustand A
Methode
Zustand B
while Warte-Bedingung mit wait
können jetzt Threads
weiterlaufen?
-> notify, notifyAll
Nach rein lesenden
Methoden nicht nötig,
da Zustand nicht
geändert wird!
•rein lesende Methoden mit oder ohne wait
•schreibende Methoden mit und ohne wait und mit und ohne
notify bzw. notifyAll
Concurrent-Klassenbibliothek aus Java 5
Thread Pool
Vorteil:
•nicht für jeden Auftrag muss neuer Thread erzeugt werden
•Anzahl der Threads nach oben beschränkbar (vrgl. Denial of Service
Angriff)
Runnable Objekte
(Aufträge)
Threads im Pool
werden in
Puffer
gelegt
holen sich die Objekte, rufen
run auf und arbeiten sie ab,
holensich neues Objekt usw.
Runnable Objekte werden ausgeführt und geben nix zurück
Für Objekte mit Methoden, die was zurückgeben: interface Callable
interface
interface Callable<V>
Callable<V> {{
VV call();
call();
}}
Threadpool (z.B. Klasse ThreadPoolExecutor) implementiert
Schnittstelle ExecutorService:
interface
interface ExecutorService
ExecutorService {{
<T>
<T> List<Future<T>>
List<Future<T>> invokeAll(Collection<Callable<T>>
invokeAll(Collection<Callable<T>> tasks);
tasks);
<T>
Future<T>
invokeAny(Collection<Callable<T>>
tasks);
<T> Future<T> invokeAny(Collection<Callable<T>> tasks);
<T>
<T> Future<T>
Future<T> submit(Callable<T>
submit(Callable<T> task);
task);
}}
•submit wartet nicht auf Ergebniss
•invokeAny kehrt zurück, wenn der 1. Auftrag zuende ist – die
anderen Aufträge werden abgebrochen
Future
Auf
Aufdas
dasErgebnis
ErgebnisVV
wird
wirdgewartet!
gewartet!
Abbruch
Abbruchmöglich
möglich
obwohl
obwohlBearbeitung
Bearbeitung
schon
schonbegonnen
begonnenhat
hat
interface
interface Future<V>
Future<V> {{
boolean
boolean cancel(boolean
cancel(boolean mayInterruptWhileRunning);
mayInterruptWhileRunning);
VV get();
get();
VV get(long
get(long timeout,
timeout, TimeUnit
TimeUnit unit);
unit);
boolean
boolean isCancelled();
isCancelled();
boolean
isDone();
boolean isDone();
}}
Timeout:
Timeout:längste
längsteZeit
Zeitdie
diegewartet
gewartetwird
wird
Unit:
Unit:Einheit
Einheitvon
vontimeout:
timeout:
TimeUnit.NANO_SECONDS
TimeUnit.NANO_SECONDS
TimeUnit.MIKRO_SECONDS
TimeUnit.MIKRO_SECONDS
TimeUnit.MILLI_SECONDS
TimeUnit.MILLI_SECONDS
TimeUnit.SECONDS
TimeUnit.SECONDS
ThreadPoolExecutor
Java Implementierung eines Thread Pool
ThreadPoolExecutor(int
ThreadPoolExecutor(int corePoolSize,
corePoolSize, int
int maximumPoolSize,
maximumPoolSize,
long
keepAliveTime,
TimeUnit
long keepAliveTime, TimeUnit unit,
unit,
BlockingQueue<Runnable>
workQueue)
BlockingQueue<Runnable> workQueue)
corePoolSize: mindest Zahl Threads im Pool (selbst wenn
unbeschäftigt
maximumPoolSize: höchst Zahl Threads im Pool
keepAliveTime: max Lebenszeit für unbeschäftigten Thread
workQueue: Warteschlange für Aufträge (obwohl nur Runnable
werden doch auch Callable Aufträge vom ThreadPool bearbeitet)
Locks und Conditions
•Lock Objekte haben Methode lock, unlock
•lockObject.lock() lässt nur einen Thread durch und schiebt weitere in
Warteschlange. lockObject ist dann gesperrt
•lockObject.unlock() lässt den nächsten Thread wieder passieren
•unlock in finally Block, sonst Dauerlock durch Exception oder return
•lock ist wie synchronized aber:
•Entsperrung durch unlock z.B. auch in anderen Methoden durch andere
Threads möglich
•tryLock versucht Sperre und kehrt sofort zurück
•Klasse ReentrantLock
•ermöglicht es demselben Thread mehrfach dieselbe Sperre zu
passieren
•ermöglicht faire Bedienreihenfolge der Threads
Lese – Schreib Sperre
Klasse ReentrantReadWriteLock implementiert ReadWriteLock
Lock readLock();
gibt Lesesperre
Lock writeLock();
gibt Schreibsperre
•eine Lesesperre wird dort angebracht, wo im Code Daten gelesen
werden
•Schreibsperre entsprechend
•Unterschied zu normaler Sperre:
•Lesesperren können (zu einem Zeitpunkt) beliebig oft gesetzt
werden, Schreibsperren nur einmal
•Lese und Schreibsperren sperren sich gegenseitig mit, wenn sie
vom demselben ReadWriteLock Objekt stammen
Bitte beachten: lesende Threads, schreibende Threads gibt es gar nicht. Der
Thread macht immer das, was er an Code in der Methode, wo er ist, gerade
vorfindet.
Condition
Methoden einer Condition
implementierenden Klasse
Object
Condition
wait
await
notify
signal
notifyAll
signalAll
•bezieht sich auf ein Lock Objekt
•von diesem Objekt wird das Condition Objekt mit
newCondition() bezogen
zu einem Lock können mehrere Condition Objekte exisitieren
•im Produce / Consumer Problem notifyAll nicht mehr nötig
•stattdessen signal mit zwei Condition Objekten notFull und
notEmpty
public
public void
void put(int
put(int x){
x){
lock.lock();
lock.lock();
try
try {{
while(count
while(count ==
== data.length){
data.length){
ConditionnotFull.awaitUninterruptibly();
ConditionnotFull.awaitUninterruptibly();
}}
Bezeichner:
Bezeichner:
data[tail++]
data[tail++] == x;
x;
wenn
notFull,
if(tail
==
data.length)
wenn notFull,
if(tail == data.length)
tail
== 0;
tail
0;
dann
dann
Nachricht
count++;
Nachrichtnur
nuran
an
count++;
weitergehen
dump();
weitergehen
dump();
auf
Füllung
auf
Füllung
notEmpty.signal();
notEmpty.signal();
Wartende
}}
Wartende(nicht
(nicht
finally
{
finally {
signalAll)
signalAll)
lock.unlock();
lock.unlock();
}}
}}
Atomic Klassen
AtomicBoolean
Atomic Klassen haben get und set
AtomicInteger
Methoden mit atomarem Zugriff
AtomicIntegerArray
AtomicLong
AtomicLongArray
AtomicReference
AtomicReferenceArray
…
Methode zum Vergleichen und Ändern auf „einen Schlag“ (atomar)
compareAndSet
Beispiel: mehrere
Threads versuchen value
auf 1 zu setzen. Wer ist
erster?
public
public boolean
boolean from0to18()
from0to18() {{
if
if (this.value
(this.value ==
== 0)
0) {{
this.value
this.value == 1;
1;
return
true;
return true;
}} else
else {{
return
return false;
false;
}}
}}
Problem: Threadumschaltung zwischen Lesen bei this.value==0 und
setzen von value bei this.value = 1 (Warum ist das ein Problem ?)
Lösung:
AtomicInteger
AtomicInteger == ……
if
if (i.compareAndSet(0,
(i.compareAndSet(0, 1)
1) {{
//
// Gewinner
Gewinner
……
}} else
else {{
//
// Verlierer
Verlierer
……
}}
Synchronisationsklassen
Semaphore
CountDownLatch
mit await warten Threads dass ein Zähler 0 wird. Mit countDown
wird Zähler dekrementiert
CyclicBarrier
Treffpunkt. Mit await warten N-1 Threads. wait Aufruf des Nten
Threads befreit alle N Threads zum Weiterlaufen
Exchanger
An einem Treffpunkt tauschen zwei Threads Datenobjekt aus.
V exchange(V x) <- Diese Methode blockiert bis zum Eintreffen
des 2. Threads. Sollte nur mit 2 Thread mit einem Exchange
Objekt betrieben werden
Queues
interface BlockingQueue
wie vom Producer Consumer Problem bekannt: put, take
offer, add: einfügen ohne warten
poll: entnehmen ohne warten
ArrayBlockingQueue
Feld, dessen Größe durch
Konstruktor festgelegt wird
SynchronousQueue
Warteschlange mit 0
Plätzen = Exchanger
unidirektional
PriorityBlockingQueue
Elemente können Comparable
implementieren. Sortierung:
kleinstes am Kopf
LinkedBlockingQueue
Liste. Auch „unendlich“ groß
möglich.
DelayQueue
Nur Elemente die Delayed mit
getDelay implementieren.
Sortierung der Warteschlange
nach Delay.
Verklemmungen
Betriebsmittel:
Objekt, auf dessen Benutzung ein Thread u.U. warten muss
z.B. wegen Locks, Semaphoren oder synchronized
Wieso kann hier eine Verklemmung auftreten?
public
public void
void transferMoney(int
transferMoney(int fromAccountNumber,
fromAccountNumber, int
int toAccountNumber,
toAccountNumber,
float
amount)
float amount)
{{
synchronized(account[fromAccountNumber])
synchronized(account[fromAccountNumber])
{{
synchronized(account[toAccountNumber])
synchronized(account[toAccountNumber])
{{
account[fromAccountNumber].debitOrCredit(-amount);
account[fromAccountNumber].debitOrCredit(-amount);
account[toAccountNumber].debitOrCredit(amount);
account[toAccountNumber].debitOrCredit(amount);
}}
}}
}}
Vorraussetzung und Bedingung für Verklemmungen
1. Die Betriebsmittel sind nur unter gegenseitigem Ausschluss
nutzbar
2. Benutzte Betriebsmittel können dem Thread nicht entzogen
werden
3. Thread besitzen bereits Betriebsmittel und fordern weitere an
Unter diesen Vorraussetzungen kommt es zur Verklemmung
genau dann wenn
Es gibt zyklische Kette von Threads, von denen jeder mindestens
ein Betriebsmittel besitzt, das der nächste Thread in der Kette
benötigt
Kreis: Thread
Rechteck: Betriebsmittel
Pfeil von Betriebsmittel zu Thread: Thread besitzt und blockiert
Betriebsmittel
Pfeil von Thread zu Betriebsmittel: Thread fordert Betriebsmittel an
Verklemmung
Vermeidung von Verklemmungen
Nicht
Nichtzu
zuvermeiden
vermeiden
1.
2.
3.
Die Betriebsmittel sind nur unter gegenseitigem
Möglich
Möglichaber
aberaufwendig
aufwendig
Ausschluss nutzbar
Benutzte Betriebsmittel können dem Thread nicht
entzogen werden
Threads besitzen bereits Betriebsmittel und fordern
weitere an
Ein Thread darf nur Betriebsmittel fordern,
Ein Thread darf nur Betriebsmittel fordern,
wenn
wenner
erkeine
keinehat
hat
Es gibt zyklische Kette von Threads, von denen jeder mindestens ein Betriebsmittel
besitzt, das der nächste Thread in der Kette benötigt
•Ein
•EinThread
Threadfordert
fordertBetriebsmittel
Betriebsmittelimmer
immerin
inder
dergleichen
gleichenReihenfolge
Reihenfolge
an
an->
->keine
keineZyklen
Zyklen(mit
(mitThreads
Threadsder
dergleichen
gleichenKlasse?)
Klasse?)
•Bei
•BeiAnforderung
Anforderungvon
vonBetriebsmittel
BetriebsmittelBedarfsanalyse,
Bedarfsanalyse,ob
obes
esim
im
schlimmsten
schlimmstenFall
Fallzu
zuVerklemmung
Verklemmungkommen
kommenkann
kann
Haben beim Philosophenproblem die Gabeln alle denselben
Betriebsmitteltyp?
Nein, denn die Philosophen Threads brauchen jeweils bestimmte
Gabeln (die benachbarten) um essen zu können.
Jede Gabel ist ein eigener Betriebsmitteltyp!
Betriebsmittelverwaltung
Threads holen sich Betriebsmittel über ResourceManager
Thread
1 0 1
int[]
release
1 0 1
Betriebsmitteltyp
BetriebsmitteltypAA
ResourceManager
(Semaphorengruppe)
int[]
int[]
acquire
Betriebsmittel
4 3 1
Betriebsmitteltyp
BetriebsmitteltypCC
BetriebsmitteltypBB Betriebsmitteltyp
Anfordern von Betriebsmitteln „auf einen Schlag“
Ein Thread darf nur Betriebsmittel fordern, wenn er keine hat
-> er fordert alles was er braucht auf einen Schlag
Verklemmung verhindert :
T
Dies kann nicht entstehen
B
B
T
sondern nur dies ->
keine Zyklen möglich
B
B
•ResourcenManager als Semaphorengruppe, Threads sorgen
selbst für Anforderung „auf einen Schlag“
•ResourcenManger führt Buch über Anforderungen der
Threads. Wird mit deren Freigaben abgeglichen
•Wenn Thread anfordert, obwohl er noch Betriebsmittel hat,
Mitteilung an Thread. Thread gibt alles frei und fordert alles
neu an
Anfordern von Betriebsmitteln gemäß vorgegebener Ordnung
•Betriebsmitteltypen werden durchnummeriert
•Thread darf nur Betriebsmittelnummer fordern, wenn sie
höher ist als alle Nummern, die er schon hat.
•Verklemmung verhindert :
Angenommen, es gibt einen Zyklus
Dann muss es diesen Teilgraphen geben:
B
Typ i1
T
B
Typ i2
•Mit obiger Bedingung gilt; i2 > i1
•Wenn man dem Zyklus folgt muss gelten i1 < i2 < i3 … <iN < i1
•Dies ist ein Widerspruch -> kein Zyklus möglich
•ResourcenManager als Semaphorengruppe, Threads sorgen
selbst für Anforderung von „immer höheren“ Typen
•ResourcenManger führt Buch über Anforderungen der
Threads. Wird mit deren Freigaben abgeglichen
•Wenn Thread zu niedrigen Typ anfordert, Mitteilung an
Thread. Thread gibt alle 5höhere Typen frei und fordert alles
neu an
Wieso löst die obige Strategie das Verklemmungsproblem bei
den Philosophen (alle Philosophen wollen linke Gabel zuerst
nehmen)?
Durch die obige Strategie wird der Philosoph mit Nummer N-1
gezwungen, die rechte Gabel zuerst zu nehmen.
Anfordern von Betriebsmitteln mit Bedarfsanalyse
Jeder Thread muss von vorneherein wissen, wie viele
Exemplare er von jedem Typ höchstens gleichzeitig braucht
max 2
max 2
Zugriff
Zugriffnicht
nichterlaubt,
erlaubt,da
daThread
ThreadAAnoch
noch
einen
einenfordern
fordernkönnte
könnte
Verklemmung
Thread A
Thread B
Belegungszustand ist genau dann sicher:
•es existiert mind. ein Thread, der noch fertig laufen kann
•mit den dann freiwerdenden Betriebsmitteln wird mind. ein Thread
fertig
•usw. bis zum letzten Thread
•Dies gilt für alle Typen gesondert
Beispiel (für nur einen Betriebsmittel - Typ):
Anzahl eines Betriebsmittels: 10
momentane Belegung:
Restforderung:
Thread A 3
Thread B 2
Thread C 2
Belegungszustand
Belegungszustand
Thread A 6
Thread B 2
Thread C 5
Anzahl freier Betriebsmittel: 3
Kann ein Thread mit seiner Restforderung zuende laufen?
Ja, nämlich Thread B: braucht 2 und 3 sind noch da.
Kann, wenn B zuende ist, ein weiterer zuende laufen?
Wenn B zuende sind 5 Betriebmittel frei – reicht für C.
Kann, wenn C zuende ist, der A zuende laufen?
Ja, da dann 7 Betriebsmittel frei sind, A aber nur noch 6 braucht.
Alle Threads können fertig werden: Zustand ist sicher!
Darf Thread A noch ein Betriebsmittel kriegen?
Anzahl eines Betriebsmittels: 10
momentane Belegung:
Restforderung:
Thread A 4
Thread B 2
Thread C 2
Thread A 5
Thread B 2
Thread C 5
Anzahl freier Betriebsmittel: 2
Kann einer mit seiner Restforderung zuende laufen ?
Ja, nämlich Thread B: braucht 2 und 2 sind noch da.
Kann, wenn B zuende ist, ein weiterer zuende laufen ?
Wenn B zuende sind 4 Betriebmittel frei – reicht weder für A noch
für C – Verklemmung möglich!
Zustand nicht sicher! A darf nichts mehr kriegen.
Petri-Netze
http://pipe2.sourceforge.net/about.html
•Petri Netz = Graph aus Stellen (Kreis), Transitionen (Rechteck)
und Pfeilen
•Pfeil darf nur von Stelle zu Transition oder umgekehrt gehen
•Stellen dürfen keins, eins oder mehrere Marken (Punkte) haben
•in sukzessiven Zeitschritten
werden Transitionen
geschaltet
•höchstens 1 Transition pro
Zeitschritt! evtl. Prioritäten
Transition
Transitiont1,
t1,t2
t2schaltet
schaltet
Transition
Transitiont3
t3schaltet
schaltetnicht
nicht
•Schaltung, wenn alle eingehende Stellen mind. eine Marke haben
•Transitionen ohne Eingänge dürfen immer schalten
•nach Schaltung:
alle eingehenden Stellen Zahl der Marken —
alle ausgehenden Stellen Zahl der Marken ++
Modellierung von Nebenläufigkeit
mit Petri-Netzen: synchronized
Transition =
Verzweigungsbefehl oder Thread Erzeugung/Methodenaufrufe,
Methodenende und Threadenden
Stelle =
Position unmittelbar vor oder nach Verzweigungsbefehl oder
Thread Erzeugung/Methodenaufruf („zwischen den Befehlen“)
Marke =
Thread oder Sperre von Objekt
Petri-Netze: synchronized
Petri-Netze: notify
Priorität
Prioritätwakeup1
wakeup1>>Priorität
Prioritätnotify2
notify2
Petri-Netze: notifyAll
Priorität
Prioritätwakeup1
wakeup1>>Priorität
Prioritätnotify2
notify2
GUI
Parallelität und grafische Benutzeroberflächen
Objektorientierte Umsetzung einer graphischen Bedienoberfläche
Rahmen (JFrame)
Knopf (JButton)
Panele (JPanel)
Etikett (JLabel)
GUI
Objektorientierte Umsetzung einer graphischen Bedienoberfläche
Zwei Arten von Komponenten:
Behälterkomponenten (Container) elementare Komponenten
Beispiel:
•Rahmen, Panele
Beispiel:
•Etiketten, Knöpfe
•Canvas / Zeichenfläche
•Auswahlkomponenten
(CheckBox, Liste)
•Rollbalken
•Textkomponenten
GUI
Objektorientierte Umsetzung einer graphischen Bedienoberfläche
Behälterkomponenten (Container)
besitzen Methoden add und remove
Fenster sind besondere Behälter:
sie „liegen ganz außen“ d.h. sie können in keinen
Behälter eingefügt werden.
Hauptfenster (JFrame)
Dialogfenster (JDialog)
sind anderem Fenster zugeordnet
können blockieren (modal)
Grafische Benutzeroberflächen starten
import
import javax.swing.*;
javax.swing.*;
public
public class
class LabelExample1
LabelExample1
{{
public
public static
static void
void main(String[]
main(String[] args)
args)
{{
JFrame
JFrame ff == new
new JFrame("Beispiel
JFrame("Beispiel für
für Label");
Label");
f.add(new
JLabel("Hallo
Welt"));
f.add(new JLabel("Hallo Welt"));
f.setLocation(300,
f.setLocation(300, 50);
50);
f.setSize(400,
f.setSize(400, 100);
100);
f.setVisible(true);
f.setVisible(true);
}}
}}
GUI
Die Ereignis Objekte enthalten Informationen über das Ereignis,
z.B. an welcher Komponente ist das Ereignis aufgetreten
(Abfrage mit getSource)
Ereignissteuerung
1. Jedes Objekt kann sich bei Komponenten
als Beobachter (Listener) von verschiedenen Ereignissorten
anmelden.
2. Wenn ein Ereignis E an einer Komponente K auftritt, werden
alle Beobachter benachrichtigt, die für E an K angemeldet sind.
GUI
Beispiel:
Anklicken von Knopf -> Ausgabe „Schaltfläche betätigt“
Beobachter Objekte für Ereignisse der Sorte ActionEvent
müssen ActionListener implementieren:
einzige Methode: actionPerformed
class Beobachter implements ActionListener {
public void actionPerformed( ActionEvent ereignis ) {
System.out.println(„Schaltfläche betätigt“);
}
}
GUI
Beispiel:
Anklicken von Knopf -> Ausgabe „Schaltfläche betätigt“
Registrieren des Beobachters an der Komponente:
...
JButton knopf = new JButton();
...
Beobachter meinBeobachter = new Beobachter();
knopf.addActionListener( meinBeobachter );
...
Tritt am Knopf ein ActionEvent auf, wird die Methode
actionPerformed des Beobachter Objekts aufgerufen.
Threads und Swing
Threads nach Starten von einem oder mehreren JFrame:
java.lang.ThreadGroup[name=main,maxpri=10]
java.lang.ThreadGroup[name=main,maxpri=10]
Thread[main,5,main]
Thread[main,5,main]
Thread[Java2D
Thread[Java2DDisposer,10,main]
Disposer,10,main]
Thread[AWT-Shutdown,5,main]
Thread[AWT-Shutdown,5,main]
Thread[AWT-Windows,6,main]
Thread[AWT-Windows,6,main]
Thread[AWT-EventQueue-0,6,main]
Thread[AWT-EventQueue-0,6,main]
Thread AWT-EventQueue heißt Event-Dispatcher
Event-Dispatcher kommt erst nach setVisible(true)
-> Es gibt für alle Ereignisse und Fenster nur einen Event Thread
Event Dispatcher
paintComponent Methoden aller Swing
Interaktionselemente
alle Listener aus allen Fenstern eines
Java Prozesses
Problem mit Event Dispatcher Thread
public
public class
class ButtonExample2b
ButtonExample2b extends
extends ButtonExample2
ButtonExample2 {{
public
public void
void actionPerformed(ActionEvent
actionPerformed(ActionEvent evt)
evt) {{
super.actionPerformed(evt);
super.actionPerformed(evt);
try
try {{
Thread.sleep(10000);
Thread.sleep(10000);
}}
catch(InterruptedException
catch(InterruptedException e)
e) {}
{}
}}
}}
Was könnte hier ein Problem sein?
Wenn über Button etwas hinweggezogen wird, wird Button nicht
neu gezeichnet!
Regel1: Ereignisbehandlung möglichst schnell. Kein sleep oder wait
•Längere Sachen (z.B. Kommunikation mit anderem Rechner) auf
anderem Thread
•Fortschritts Meldungen an GUI zurück
•Aber Problem: Swing Methoden sind im Allgemeinen nicht threadsafe (= nicht für Benutzung durch parallele Threads geeignet)
Regel2: Zugriff auf Elemente der grafischen Oberflache nach
setVisible(true) nur durch Event-Dispatcher!
Andere Threads beauftragen Event-Dispatcher mit
EventQueue.invokeLater oder EventQueue.invokeAndWait
Thread
Threadmacht
machtdirekt
direktweiter
weiter
public
public class
class EventQueue
EventQueue {{
public
public static
static void
void invokeLater(Runnable
invokeLater(Runnable doRun)
doRun) {...}
{...}
public
public static
static void
void invokeAndWait(Runnable
invokeAndWait(Runnable doRun)
doRun) {...}
{...}
}}
Thread
Threadwartet,
wartet,bis
bisdoRun
doRun
fertig
fertigist
ist
class
class IncreaseTask
IncreaseTask implements
implements Runnable
Runnable {{
...
...
public
public void
void run()
run() {{
clock.increase(amount);
clock.increase(amount);
}}
}}
class
class Ticker
Ticker extends
extends Thread
Thread {{
...
...
public
public Ticker(Clock
Ticker(Clock clock)
clock) {{
incrTask
incrTask == new
new IncreaseTask(clock,
IncreaseTask(clock, TICK_TIME
TICK_TIME // 1000.0);
1000.0);
start();
start();
}}
public
public void
void run()
run() {{
try
try {{
while(!isInterrupted()
while(!isInterrupted() {{
EventQueue.invokeLater(incrTask);
EventQueue.invokeLater(incrTask);
Thread.sleep((int)
Thread.sleep((int) TICK_TIME);
TICK_TIME);
}}
}}
catch(InterruptedException
catch(InterruptedException e)
e) {}
{}
}}
}}
Situation bei Grafiken
Neuzeichnen einer Grafik soll durch Thread angestoßen werden
repaint aufrufen -> Event Dispatcher ruft paintComponent auf
repaint ist thread safe
Grafikprogrammierung
public
public class
class DrawingExample
DrawingExample extends
extends JPanel
JPanel {{
public
public void
void paintComponent(Graphics
paintComponent(Graphics g){
g){
super.paintComponent(g);
super.paintComponent(g);
g.setColor(Color.RED);
Nicht
vom
Programm
g.setColor(Color.RED);
Nicht
vom
Programm
g.drawLine(30,
g.drawLine(30, 200,
200, 200,
200, 200);
200);
aufrufen,
……
aufrufen,sondern
sondernvom
vom
}}
Event-Dispatcher!
Event-Dispatcher!
}}
MVC (Model, View, Controller)
Listener
Controller
Interaktionselemente
Listener
View
Modell
Socket Programmierung
Steuerung
logischer
Verbindung
en, RPC
Wohl mitunter einer der populärsten Sprüche lautet
„Please Do Not Throw Salami Pizza Away“ (Physical
Layer, Data Link Layer, usw.), eine deutsche Variante ist
„Alle deutschen Schüler trinken verschiedene Sorten
Bier“ (Anwendungsschicht, Darstellungsschicht, …).
Datagramm steht als Oberbegriff für
OSI-Schicht
Schicht 2
Schicht 3
Schicht 4
Datagrammbezeichnung
Datenframe
Datenpaket
Datensegment
Socket Programmierung
Kommunikation von unterschiedlichen Prozessen auf
verschiedenen Rechnern
Adressierung auf der IP-Adresse
Vermittlungs Schicht
Rechner
Adressierung auf der Portnummer
Transport Schicht
Prozess auf Rechner
IP-Adresse und Rechnernamen
•IP-Adresse v4 32bit (v6 128bit)
•je 8 bit als Dezimalzahl: z.B. 143.93.53.147
•Für Menschen besser Rechnernamen: z.B. www.hs-furtwangen.de
•Domain Name System setzt Rechnernamen in IP-Adressen um
•Adresse 127.0.0.1 oder Rechnername localhost ist immer der
eigene Rechner
Transportprotokolle
UDP (User Datagramm Protocol)
TCP (Transmisstion Control
Protocol)
Verbindungslos
Verbindungsorientiert
Verlust von Paketen oder
Vertauschungen in der Reihenfolge
möglich
Kein Verlust oder Vertauschung
von Daten. Flusskontrolle,
Überlastkontrolle
Ankommende Daten werden an die
Anwendungen verteilt
Dazu fügt UDP der IP Funktionalität
eine Portnummer hinzu
Ankommende Daten werden an die
Anwendungen verteilt.
Portnummern
Alle Daten werden in einem Datagramm Datenstromorientiert. (Pipe)
versendet mit Ziel- und
Quellportnummer (MessageQueue)
Protokoll für Audio, Video Daten
www, email
Multicast möglich
kein Multicast
Verbindungsauf- und abbau entfällt
Verbindungsauf- und Ab-bau
Socket
Anwendungsschicht
Betriebssystem
Socket - Schnittstelle
Transportschicht
Anwendungen
Standarddienste:
Standarddienste:
wohlbekannte
Portnummern
wohlbekannte
Portnummern
Server
Client
Client
Client
Client
Client
beliebige
beliebigePortnummern
Portnummern
Binden von Portnummer an Socket (gilt für TCP und UDP):
•Datagramme über Socket senden ->Datagramme
Quellportnummer= Socketnr
•Datagramme über Socket empfangen ->Datagramme
Zielportnummer= Socketnr
TCP und UDP Portnummern sind unabhängig voneinander
UDP
TCP
Verbindungsaufbau mit IPAdresse+Port (solange blocked!)
Weitere Anfragen ohne IPAdresse+Port
Server Antwort über die Verbindungsannahme = Erzeugung von
IP-Adresse+Port neuem Socket nur für diese Verbindung
– alle diese Sockets haben die gleiche
der Anfrage
Portnummer!
Client Anfrage mit IPAdresse+Port
senden
Wie
Wiekommen
kommendie
dieTCP
TCPDaten
Datenan
anden
denrichtigen
richtigenSocket?
Socket?
a)
a) Verbindungsaufbausegment
Verbindungsaufbausegmentsind
sindspeziell
speziell
gekennzeichnet
gekennzeichnet––kommen
kommenzum
zum„ServerSocket“
„ServerSocket“
b)
b) Daten
DatenSegmente
Segmentehaben
habenjajaQuellport
Quellportund
undkönnen
können
dem
demrichtigen
richtigenSocket
Socketzugeordnet
zugeordnetwerden
werden
Behauptung einerEins zu eins Zuordnung von Socket zu Port ist nicht
korrekt!
UDP in Java
byte[]
byte[]
DatagramSocket
+send(DatagramPacket)
+receive(DatagramPacket)
DatagramPacket( buffer,
buffer.length,
receiveAdress
receivePort)
blockierend
blockierend
wirkt wie der Speicher für eine
Nachricht einer MessageQueue
Clientseitigs schliessen der Verbindung über Timeout
(Methode von DatagramSocket) soll verhindern, dass bei
verlorengeheden Kommandos der Client hängenbleibt
UDP in Java in Aktion
UDPSocket (utility Programm) im Source Code:
Server im Source Code:
Client im Source Code:
Start Server
Start Client
TCP in Java
erzeugt
erzeugtSocket
Socketfür
fürVerbindung
Verbindung
ServerSocket
+accept
}
nur Server !
byte
byteStröme
Ströme
Socket
+getInputStream
+getOutputStream
Erzeugen von Socket
Objekt stellt TCP
Verbindung her
InputStream (empfangen)
OutputStream (senden)
Buffered... Ströme können Laufzeit
um mehrere Größenordnungen
verbessern
Senden von Strings: ein Datenstrom wird geschickt wird. Evtl.
mehrere read Aufrufe für einen String nötig -> Trennzeichen sind
nötig z.B. NewLine (wie bei HTTP, SMTP, POP)
Strom zum Lesen (Schreiben analog):
Socket
Socket ss == ...;
...;
BufferedReader
BufferedReader br
br == new
new BufferedReader(new
BufferedReader(new InputStreamReader(
InputStreamReader(
s.getInputStream()));
s.getInputStream()));
Lesen:
String
String message
message == br.readLine();
br.readLine();
Schreiben:
bufferedWriter.write(„hallo,
bufferedWriter.write(„hallo, Welt“);
Welt“);
bufferedWriter.newLine();
bufferedWriter.newLine();
bufferedWriter.flush;
bufferedWriter.flush;
Rückgabewert null =
Stromende -> Partner hat
TCP Verbindung
geschlossen
TCP in Java in Aktion
Server im Source Code:
Client im Source Code:
Start Server
Start Client
Kommandos increment und reset können auch direkt per telnet
localhost 1250 (putty) geschickt werden!
Sequentielle und parallele Server
UDP
Kann abwechselnd Kommandos von verschiedenen Clients
ausführen
TCP
Ist an einen Client solange gebunden, bis der die Verbindung
schliesst
Wenn der Client nichts tut, liegt der Server brach
Statische Parallelität:
mehrere parallele Threads teilen sich die Arbeit – Schalter /
Kunden System
Dynamische Parallelität:
Für jeden Kunden wird ein Thread erzeugt. Bei
Verbindungsende endet Thread. Threadanzahl variiert ständig
Mischformen möglich
Beispiel für dynamischen Server
TCP Server erhält Zahl: soviel Sekunden wird er blockiert, dann
echot er die Zahl
serverSocket
serverSocket == new
new ServerSocket(1250);
ServerSocket(1250);
while(true)
äußereSchleife
Schleife––Server
Serverwartet
wartetauf
aufAnfragen
Anfragen
while(true) {{ äußere
tcpSocket
tcpSocket == new
new TCPSocket(serverSocket.accept());
TCPSocket(serverSocket.accept()); blockiert
blockiert
statisch
while(true) {
String request =
tcpSocket.receiveLine();
int secs =
Integer.parseInt(request);
Thread.sleep(secs*1000);
tcpSocket.sendLine(); echo
}
Schließen weggelassen
}}
dynamisch
new Slave(tcpSocket);
Erzeugung ohne
Referenzvariable -> wenn
Thread stirbt dann auch Objekt
dynamisch
class
class Slave
Slave extends
extends Thread
Thread {{
public
public Slave(TCPSocket
Slave(TCPSocket socket)
socket) {{
this.socket
this.socket == socket;
socket;
this.start();
this.start();
}}
public
public void
void run()
run() {{
String
String request
request == socket.receiveLine();
socket.receiveLine();
while(request!=null)
while(request!=null) {{ Stromschlusszeichen,
Stromschlusszeichen,wenn
wennClient-close
Client-close
request
=
tcpSocket.receiveLine();
request = tcpSocket.receiveLine();
int
int secs
secs == Integer.parseInt(request);
Integer.parseInt(request);
Thread.sleep(secs*1000);
Thread.sleep(secs*1000);
tcpSocket.sendLine();
tcpSocket.sendLine();
}}
socket.close();
socket.close();
}}
}}
Paralleler und sequentieller Server in Java in Aktion
SequentialServer
im Source Code:
Paralleler Server im
Source Code:
Client im Source Code:
Start Seq. Server
Start Client
Start Paralleler Server
Paralleler und sequentieller Server in Java in Aktion
Gefahr bei dynamischer Parallelität: Denial-of-Service-Angriff
viele Threads auf Server erzeugen -> Rechner überlastet
Lösung: Beschränkung der Thread Anzahl z.B. mit Thread Pool
serverSocket
serverSocket == new
new ServerSocket(1250);
ServerSocket(1250);
ThreadPoolExecutor
ThreadPoolExecutor pool
pool ==
new
new ThreadPoolExecutor(3,3,0L,TimeUnit.SECONDS,
ThreadPoolExecutor(3,3,0L,TimeUnit.SECONDS,
new
new LinkedBlockingQueue<Runnable>());
LinkedBlockingQueue<Runnable>());
while(true)
while(true) {{
tcpSocket
tcpSocket == new
new TCPSocket(serverSocket.accept());
TCPSocket(serverSocket.accept());
dynamisch
Task task = new Task(tcpSocket, ...);
pool.execute(task);
}}
class
class Task
Task extends
extends Runnable
Runnable {{
public
public void
void run()
run() {{
while(true)
while(true) {{
String
String request
request == tcpSocket.receiveLine();
tcpSocket.receiveLine();
...
...
tcpSocket.sendLine(„“+result);
tcpSocket.sendLine(„“+result);
}}
}}
•Mit Socket Programmierung kann man HTTP, SMTP, POP3,
IMAP etc. programmieren
•In Java Klassenbibliothek gibt schon Utility Klassen für
HTTP (URL, URLConnection) und für SMTP und POP3 oder
•IMAP Oder im Internet suchen
Verteilte Anwendungen mit RMI (Remote Method Invocation)
Was ist RMI?
•Verteiltes System = Programm läuft auf mehreren Rechnern
•Objektorientierung: Objekte senden sich Nachrichten
•Idee: Objekte auf Rechner verteilen
•Objekte senden sich Nachrichten über Rechnergrenzen hinweg
(entfernt)
Warum RMI?
•Objektorientierter als Sockets
•Ob Objekte auf einzigen Rechner oder auf vielen Rechnern soll
möglichst wenig auffallen
•Damit ist Verteilung „transparent“ (Verteilung ist durchsichtig =
nicht sichtbar)
•Daher RMI -> einfachere Entwicklung als mit Sockets
Stub und Skeleton
•Stub und Skeleton: damit entfernter Methodenaufruf aussieht wie
lokaler
•Stub ist ein Stellvertreter Objekt des entfernten Objekts
•Stub-Methoden aufrufbar wie Methoden von lokalem Objekts
•Skeleton ruft entsprechende entfernte Methoden auf entsprechendem
entferntem Objekt auf
•Kommunikation über TCP/IP ist verborgen
•Stub und Skeleton auch verborgen:
•Stub wird automatisch erzeugt
•Skeleton ist Programmteil in RMI Impelementierung
•Warum wird Remote erweiterndes interface gebraucht?
•Stub und Originalobjekt sind zwei verschiedene Objekte, die
aber die gleichen entfernten Methoden haben müssen!
Vorgehen
1. Welche Objekte werden von anderen Rechnern aus
angesprochen? (= entfernte Objekte)
2. Für entfernte Objekte interface mit allen entfernt aufrufbaren
Methoden deklarieren
• Schnittstelle extends Remote
• Alle Methoden: throws RemoteException
3. Schnittstelle implementieren.
extends UnicastRemotObject = Benutzung von außen erlaubt
4. Server programmieren:
• erzeugt entfernte Objekte und meldet sie bei registry an
5. Client programmieren
• Stubs über registry holen
• Verwendet Schnittstellen-Methoden wie lokale Methoden
6. registry auf Server Rechner starten
Beispiel: Counter per RMI
Entferntes Objekt:
interface
interface Counter
Counter extends
extends Remote
Remote {{
int
int reset()
reset() throws
throws RemoteException;
RemoteException;
int
increment()
throws
int increment() throws RemoteException;
RemoteException;
}}
Rückgabe:
Rückgabe:aktueller
aktuellerZählerstand
Zählerstand
public
public Class
Class CounterImpl
CounterImpl extends
extends UnicastRemoteObject
UnicastRemoteObject implements
implements Remote
Remote {{
public
public CounterImpl()
CounterImpl() throws
throws RemoteException
RemoteException {}
{}
...
...
}}
parameterloser
parameterloserKonstruktor
Konstruktornötig
nötig
Server:
public
public Class
Class Server
Server {{
...main...{
...main...{
CounterImpl
CounterImpl myCounter
myCounter == new
new CounterImpl();
CounterImpl();
Naming.rebind(„Counter“,
Naming.rebind(„Counter“, myCounter);
myCounter);
}}
}}
Anmeldung von myCounter Objekt bei
Anmeldung von myCounter Objekt beiregistry
registryunter
unterder
der
Adresse
Adresse„Counter“
„Counter“
Client:
public
public Class
Class Client
Client {{
...main...
...main... {{
Counter
Counter myCounter
myCounter ==
(Counter)
(Counter) Naming.lookup(„rmi://localhost/Counter“);
Naming.lookup(„rmi://localhost/Counter“);
int
result
=
myCounter.reset();
int result = myCounter.reset();
...
...
}}
•Holen
von
Counter
Objekt
bei
registry
unter
der
Adresse
•Holen
von
Counter
Objekt
bei
registry
unter
der
Adresse
}}
„Counter“
„Counter“
•localhost
•localhoststeht
stehtfür
fürRechnername
Rechnername
•Das
•Dasentfernte
entfernteObjekt
Objektkann
kannwie
wieein
einlokales
lokalesObjekt
Objektunter
unter
der
derReferenz
ReferenzmyCounter
myCounterbenutzt
benutztwerden
werden
•Der
•DerRückgabetyp
Rückgabetypvon
vonlookup
lookupist
istRemote
Remoteund
undmuss
mussauf
auf
den
deninterface
interfaceTyp
TypCounter
Countergecastet
gecastetwerden
werden
GUIClient:
•in actionPerformed NICHT entfernte Methoden aufrufen – sonst
Gefahr des GUI Einfrieren!
•Aufruf in eigenem Thread
•Aktualisieren der GUI nur über Einspeisen von Runnable Objekt in
EventQueue (mit invokeLater(Runnable))
•In run() von Runnable Objekt wird GUI aktualisiert
Wichtiger praktischer Hinweis
rmiregistriy muss in einem Verzeichnis gestartet werden, das die
Klassenpfadumgebungsvariable angibt z.B. mit
set classpath=.;pada\da\rmi\counter
Paralleler und sequentieller Server in Java in Aktion
Server
im Source Code:
Start registry
Client im Source Code:
Start Server
Entferntes Objekt:
Start Client
Wie funktioniert RMI?
•Anmelden = Zuordnung
von Server Portnummer,
Kennung Obj-id etc. zu
Namen z.B. Counter
•Stub holt über
wohlbekannte
Portnummer 1099 und
Rechnername von lookup
den Server-Port, Objekt
Kennung etc. über TCP
•Wenn 1099 schon belegt:
rmiregistry <neue Portnummer>
clientseitig: lookup(rmi://localhost:<neue Portnummer>/Counter)
•Starten von registry aus Programm:
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind(„Counter“, new CounterImpl());
Wenn jeder Server seine eigene Registry haben soll ->
jede Registry braucht eine eigene Portnummer (wie soll der
Client sonst die für ihn passende registry finden?)
Parallelität bei RMI Aufrufen
aufruf 1
Client
objekt1
aufru
f2
Server
objekt2
Werden aufruf 1 und aufruf 2 vom Server parallel oder sequentiell
abgearbeitet?
Parallelität bei RMI Aufrufen
aufruf 1
Client
Objekt 1
aufru
f2
Server
Objekt 2
•aufruf 1 und aufruf 2 werden in unterschiedlichen Threads parallel
abgearbeitet! (objekt1 und objekt2 können auch von derselben
Klasse sein
•Im Vergleich zum parallelen TCP Server müssen keine Threads
erzeugt und gestartet werden, da dies bereits automatisch gemacht
wird
Was ist hier beim Programmieren zu beachten?
()
s
a
W
h
c
ma
Client
s()
a
W
mach
Objekt 1
Server
•aufruf 1 und aufruf 2 von machWas() werden parallel in zwei
Threads durchgeführt
•Die Methode machWas() muss eventuell synchronized sein
Wertübergabe für Parameter und Rückgabewerte
•Parameter und Rückgabewerte mit primitiven Datentypen werden
als Kopie übergeben (call by value)
•-> Änderungen auf der Kopie haben keine Auswirkungen auf das
Original
•Parameter und Rückgabewerte mit Objekt – Datentypen werden als
Kopie übertragen, wenn sie KEINE entfernten Objekte sind (d.h.
nicht UnicastRemoteObject erweitern bzw. ein Remote interface
implementieren
•Entfernte Objekte werden als Referenz übertragen (call by
reference), wobei beim Empfänger des Objekts ein Stub erzeugt wird
(bei jedem Aufruf ein neuer Stub!)
•Werden von solchen Objekten Methoden beim Empfänger
aufgerufen (callback) wird beim Sender KEINE Registry benötigt,
über die der Empfänger das entfernte Objekt identifizieren müsste
Objektreferenz bei nicht entfernten Objekten
•Parameter und Rückgabewerte mit Objekt – Datentypen werden als
Kopie übertragen, wenn sie KEINE entfernten Objekte sind d.h. nicht
UnicastRemoteObject erweitern bzw. ein Remote interface
implementieren
•-> Änderungen auf der Kopie haben keine Auswirkungen auf das
Original
•Das gesamte Objektgeflecht wird serialisiert übertragen
•-> alle Klassen des Geflechts müssen Serializable implementieren,
sonst Exception
•Serializable schreibt keine Methoden vor
•Attribute, die als transient gekennzeichnet sind, werden nicht
serialisiert
•Übergeben einer Kopie dann sinnvoll, wenn Client Datenstruktur
an Server zur Auswertung oder Speicherung
•Rein lesender Zugriff
•Objekt Serialisierung = Verwandlung von Attributen in byte
Folgen
Objektreferenz bei entfernten Objekten
•Parameter und Rückgabewerte mit Objekt – Datentypen werden als
Referenzen übertragen, wenn sie entfernten Objekte sind ->
Änderungen geschehen im Original
•Entfernte Objekte werden als Referenz übertragen (call by
reference), wobei beim Empfänger des Objekts ein Stub erzeugt wird
(bei jedem Aufruf ein neuer Stub!)
•Werden von solchen Objekten Methoden beim Empfänger
aufgerufen (callback) wird beim Sender KEINE Registry benötigt,
über die der Empfänger das entfernte Objekt identifizieren müsste
•r‘ zeigt auf Stub
•Übergabe als Referenz nötig, wenn Server Datenstruktur auf dem
Client ändern sollen
•Übergabe als Referenz nötig, wenn Seiteneffekt auf dem Client
auftreten soll (z.B. Ausgaben auf Bildschirm)
Beispiel für Objektreferenz bei entfernten Objekten:
Chatten
Irgendein Client sagt was -> Server verteilt es auf die anderen Clients
ChatServerMain
•Erzeugt ChatServer
•Anmeldung ChatServer bei
Registry
ChatClientMain
•Holt Stub Referenz auf ChatServer
•Erzeugt ChatClient und ChatGUI
•Fügt ChatClient beim ChatServer
hinzu (addClient(ChatClient))
•Jeder ChatClient hat einen Namen
(Nick Name)
ChatServerImpl
•Speichert alle ChatClients (bzw. deren Stubs) in einer Liste
•Wenn ChatClient mit Namen schon da, wird Anmeldung abgelehnt
-> dazu werden alle Clients mit getName nach ihrem Namen gefragt
•Wenn dabei ein Client nicht mehr existiert (RemoteException),
wird Client aus Liste entfernt (mit iter.remove damit beim Iterator
nix durcheinander kommt)
String
String name
name == objRef.getName();
objRef.getName();
for
(Iterator<ChatClient>
for (Iterator<ChatClient> iter
iter == allClients.iterator();
allClients.iterator();
iter.hasNext();)
{
iter.hasNext();) {
ChatClient
ChatClient cc
cc == iter.next();
iter.next();
try
try {{
if
if (cc.getName().equals(name))
(cc.getName().equals(name)) {{
return
return false;
false;
}}
}} catch(RemoteException
catch(RemoteException e)
e) {{
iter.remove();
iter.remove();
}}
}}
allClients.add(objRef);
allClients.add(objRef);
•Bei jedem Aufruf mit einem ChatClient Objekt als Parameter
wird ein NEUER Stub gemacht
•Wieso wird mit remove(Stub) in der Liste der alte Stub zum
entfernen wiedergefunden?
•Weil beim Suchen in der List nicht mit == sondern mit equals
verglichen wird
•Zwei Stubs liefern mit equals dann true, wenn Sie Stellvertreter
für dasselbe Objekt sind (selbst wenn == false liefert)
ChatClientImpl extends UnicastRemoteObject
ChatServerImpl extends UnicastRemoteObject
String getName()
void print(String msg)
Updater implements Runnable
JTextArea
ChatGUI implements ActionListener
ChatServerCaller extends Thread
void sendMessage(String clientName, String msg)
void removeClient(ChatClient)
Warum gibt es Updater und
ChatServerCaller?
•Updater: GUI EventQueue mit
invokeLater
•ChatServerCaller: damit die
actionPerformed Methode nicht
einfriert
ChatGUI
•Beenden des Clients mit „Ende“
•Damit der Thread zum Abmelden beim Server noch Zeit zum
Zuendelaufen hat, wird 100ms gewartet, bevor System.exit(0)
public
public void
void actionPerformed(ActionEvetn
actionPerformed(ActionEvetn et)
et) {{
////Abmelden
Abmeldenbeim
beimServer
Server
ChatServerCaller
ChatServerCaller caller
caller == new
new ChatCallerServer(
ChatCallerServer( ...);
...);
caller.start();
caller.start();
try
try {{
caller.join(100);
caller.join(100);
}} catch(InterruptedException
catch(InterruptedException e)
e) {}
{}
System.exit(0);
System.exit(0);
}c
}c
•RMI Registry muss nur auf Server gestartet werden
•Server erhält Referenzen auf Client Objekt mit addClient und nicht
durch Naming.lookup
RMI Chat Server in Java in Aktion
ChatServerImpl
im Source Code:
ChatClientImpl
im Source Code:
Start registry
Start Server
ChatGUI + ChatServerCaller
im Source Code
Start Client 1
Start Client 2
Transformation lokaler in verteilte Anwendungen mit RMI
•Passive Klassen werden zu entfernten Objekten
•Semaphor für verschiedene Prozesse auf verschiedenen Rechnern:
•Semaphor wird zu entferntem Objekt
interface
interface Semaphore
Semaphore extends
extends Remote
Remote {{
void
void p()
p() throws
throws RemoteException;
RemoteException;
void
p()
throws
RemoteException;
void p() throws RemoteException;
}}
•gegenseitiger Ausschluss mit verschiedenen Prozessen möglich
•vorgegebene Ausführungsreihenfolge mit verschiedenen
Prozessen möglich
Asynchrone Kommunikation mit RMI
•MessageQueue oder Pipe wird zu entferntem Objekt
•Asynchrone Kommunikation: auch nach Ende des Senders einer
Nachricht kann ein neu startender Empfänger die Nachricht noch
empfangen (z.B. durch MessageQueue auf eigenem Rechner)
•Message Oriented Middleware (MOM): kommerzielle Software mit
Unterstützung für Transaktion und Persistenz
Verteilte MVC Anwendungen mit RMI
•Control Objekte: Aufruf von entfernten Methoden in eigenem Thread
-> kein Einfrieren GUI
•View Objekte über RMI Aufrufe bei Model als Listener registriert
Event Dispatcher Thread
entferntes
entferntesObjekt
Objekt
entferntes
entferntesObjekt
Objekt
View
Model
Control
Thread
Exportieren von Objekten
•Dynamisches Umschalten zwischen Wert- und Referenzübergabe
Entfernt-machen von Objekt auch durch „exportieren“ möglich
class
class UnicastRemoteObject
UnicastRemoteObject extends
extends RemoteServer
RemoteServer {{
static
static Remote
Remote exportObject(Remote
exportObject(Remote obj,
obj, int
int port)
port)
throws
RemoteException
throws RemoteException {{ ...
... }}
...
...
}}
startet
Thread
ServerSocket / port x
hört für obj
lokale Referenz obj-Kennung
dies ist NICHT die Registry!
kann mehrere Objekte enthalten
Exportieren von Objekten
•Konstruktoren von UnicastRemoteObject rufen exportObjet auf ->
Konstruktoren abgeleiteter Klassen müssen RemoteException werfen
•Mit unexport(Remote obj, boolean force) Rückgängig machen
möglich
•Eine Referenzübergabe ist Wertübergabe (Kopie) eines Stubs!
1. Objekt implementiert Remote und ist exportiert: ein Stubobjekt
wird serialisiert übergeben (call by reference)
2. 1. trifft nicht zu, aber Objekt implementiert Serializable -> Objekt
wird serialisiert kopiert (call by refernce)
3. Weder 1. noch 2. -> Exception bei Übergabe
Nachteil gegenüber extends UnicastRemoteObject: hashCode(),
equals(), toString() sind nicht automatisch implementiert - schlecht
bei Hashtables vrgl. ChatServer Tabelle
Stub und Registry
Server-Programm
Naming.rebind(stub)
RMI-Registry
Name des
Server Rechner Adresse
Objekts für
Portnummer des Server
stub
Registry
objekt - Kennung
Kopieren des
stub
in Registry
kann über objektKennung das
richtige Objekt
und Methoden
ansprechen
Kopieren des
stub
auf Client
Client-Programm
Naming.lookup()
stub
•RMI Objekte sind für CORBA Clients aufrufbar
•RMI geht auch über HTTP (falls andere Ports von der Firewall
blockiert werden)
Migration von Objekten
public
public class
class CounterImpl
CounterImpl implements
implements Counter,
Counter, Serializable
Serializable {{
public
public Counter
Counter comeBack()
comeBack() throws
throws RemoteException
RemoteException {{
UnicastRemoteObject.unexportObject(this,
UnicastRemoteObject.unexportObject(this, true);
true);
return
this;
return this;
}}
...
...
}}
da
damit
mitcomeBack
comeBackgerade
geradeein
einRMI
RMIAufruf
Aufrufläuft,
läuft,muss
mussdas
das
Unexportieren
Unexportierenerzwungen
erzwungenwerden
werden
public
public class
class MigratorImpl
MigratorImpl extends
extends UnicastRemoteObject
UnicastRemoteObject
implements
implements Migrator
Migrator {{
public
public Counter
Counter migrate(Counter
migrate(Counter counter)
counter) throws
throws RemoteException
RemoteException {{
UnicastRemoteObject.exportObject(counter,
UnicastRemoteObject.exportObject(counter, 0);
0);
return
counter;
return counter;
}}
...
...
System
}}
Systemkann
kannPortnummer
Portnummerselbst
selbstwählen
wählen
•Mediator reicht die Methoden von Counter durch
•zusätzlich migrate()
public
public class
class Mediator
Mediator {{
private
private Counter
Counter counter;
counter;
public
void
migrate(String
public void migrate(String host)
host) throws
throws RemoteException
RemoteException {{
Migrator
Migrator migrator
migrator == (Migrator)
(Migrator) Naming.lookup(„rmi://“
Naming.lookup(„rmi://“ ++ host
host ++
„/Migrator“);
„/Migrator“);
counter
counter == migrator.migrate(counter);
migrator.migrate(counter);
}}
...
...
}}
Objekt Migration in Java in Aktion
Mediator
Client
CounterImpl
MigratorImpl
im Source Code: im Source Code: im Source Code im Source Code
Start Server
Start Client
Server
Laden von Klassen über das Netz
Client
interface U extends Remote
Client kennt U, weil:
m(X x);
U u = (U) Naming.lookup(...)
möglich sein muss
class V implements U
m(X x) {x.k();}
Client kennt X, weil
X x = new X();
u.m(x);
möglich sein muss
class Y extends X
k() // überschreiben von k
class X
k()
Server kennt Y nicht,
muss aber k aus Y
ausführen können
wenn Client m(X x) auf Y
Objekt aufruft
-> Klassencode nachladen
Skeleton
Seit java 1.2 gibt es eine generische Skeleton Klasse, die mit
Reflektion aus den vom Client geschickten (String) Angaben
über Objekt-Kennung, Methodenname, Parameterliste die
richtige Klasse und Methode auf dem Server besorgt und
aufruft
Stub
Seit Java 5 werden Stubs dynamisch generiert. Zuerst ein Class
Objekt, dass alle Methoden aller Schnittstellen des entfernten
Objekts hat. Es existiert keine Class Datei auf der Festplatte! Von
dem Class Objekt wird ein Objekt – der Stub generiert. Alle
Methoden des Stub rufen die Methode invoke eines
InvocationHander Objekt auf, dass die Parameter und
Methodenname und Obj. Kennung an RMI Server schickt
Sicherheit
Verschlüsselung mit SSL (Secure Socket Layer)
exportObject
UnicastRemoteObject - Konstruktor
•Variante mit Parametern RMIClientSocketFactory und
RMIServerSocketFactory nutzen
•Als Parameter eigene Ableitungen dieser Klassen
benutzen oder SslRMIClientSocketFactory und
SslRMIServerSocketFactory
RMI - Aktivierung
•RMI-Server erst dann starten, wenn er gebraucht wird + nach langer
Pause beenden
•RMI-Server nach Absturz automatisch neu starten
rmid-Aktivierungsserver Programm:
Anmeldung von RMI-Klassen bei rmid-Server
selbst geschriebener
Server
RMI-Klasse
Activatable.register(desc);
anmelden
rmid-Server
spezielles RMI-Objekt
eintragen
Ende
Registry
anfordern
Client
aktivieren
RMI-Server
Log Datei
für Wiederstart
http://java.sun.com/j2se/1.4.2/docs/guide/rmi/activation/activation.1.html
Verteilte Abfallsammlung
Client 2
Client 1
Stub 2
Stub 1
RMI-Server
lokale Referenz
RMI-Objekt
•Stubs senden periodisch Signal an
Server
•Wenn timeout bei Signal
überschritten -> Stub existiert nicht
mehr
•keine lokalen und entfernten
Referenzen -> RMI-Objekt wird
gelöscht
Webbasierte Anwendungen mit Servlets und JSP
Dyamisches / Statisches Web
statisch
dynamisch
html Seite
HTTP (Hyper Text Transfer Protocol)
Browser erhält eine URL (Universal Resource Locator):
<Protokoll>:// <Servername> :<Portnummer><Pfad zur Resource>
TCP Verbindung zum HTTP-Serverprogramm
•HTTP ist ein ASCII Protokoll
•Anfrage / Antwort Paradigma
Browser sendet dann z.B. folgende HTTP Anfrage (HTTP-Request):
GET /eva/hallo.html HTTP/1.0
<Leerzeile! = Ende der Anfrage>
Antwort des Browsers (HTTP-Response)
Statuscode:
Statuscode:
HTTP/1.1 200 OK
2xx
2xx==alles
allesin
in
<Name>:<Wert>
Ordnung
Ordnung
<Name>:<Wert>>
4xx
4xx==Fehler
Fehler
...
<Leerzeile = Trennung von Head und Datenteil>
<Datenteil z.B. eine HTML Seite>
Connection:close im Header -> Server schließt unmittelbar nach
Antwort TCP Verbindung
HTTP 1.1 schließt Verbindung nicht gleich, sondern wartet ein paar
Sekunden auf neue Anfragen
Browser teilt Protokollfähigkeit mit:
GET /eva/hallo.html HTTP/1.1
Host: <Rechnername>:<Portnummer>
<Leerzeile! = Ende der Anfrage>
Sollte man das dann
noch Rechnername
nennen?!
Mehrere Rechnernamen können auf dieselbe IP-Adresse abgebildet
werden! Host sagt dann, welcher host nun tatsächlich gemeint ist.
Wie ist es, wenn ein
HTTP 1.0 Browser
anfragt?!
Formulare
HTML
An
Andiese
dieseURL
URLwird
wirdFormular
Formulargesandt
gesandt
rel.
rel.Adressierung
Adressierung==URL-Pfad
URL-Pfadvon
vonDokument
Dokument++
action
actionWert
Wert
bei
beigleichem
gleichem
Namen
Namendarf
darfnur
nurein
ein
Radiobutton
Radiobutton
gedrückt
gedrücktsein
sein
HTTP
GET/eva/Tee?Besteller=Rainer+Oechsle&Kreditkartennummer=
4711&Teesorte= Assam&Eile=onHTTP/1.1 Accept:*/* ...
POST
Anderer
AndererHTTP
HTTPBefehl!
Befehl!
HTML
<form method=„post“ action = „Tee“
HTTP
POST /eva/Tee HTTP/1.1
...
Content-Type:application/x-www-form-urlencoded
Content-Length:76
Connection:Keep-Alive
Header
Leerzeile
Besteller=Rainer+Oechsle&Kreditkartennummer=4711&Teesorte=Assam&Eile=on
Datenteil
GET
in Favoritenliste speicherbar, da Anfrage „in URL gecodet“
Nachteil: URL auf Bildschirm -> z.B. Kreditkartennr. sichtbar
Datenmenge begrenzt
POST
Anfrage nicht auf Bildschirm sichtbar
Datenmenge nicht begrenzt
Servlets
(Teil der Java EE Distribution)
Vergleich Applets / Servlets
Applets
Servlets
Für www gedacht
Keine main Methode
Keine selbstgeschriebene
–Erzeugung von Objekten
–Aufruf von ...let Methoden
–dies macht die Laufzeitumgebung
GUI
kein GUI
Datenanzeige
Datenproduktion (HTML /
Bilddaten)
Ausgeführt von Web-Server
Ausgeführt von Client-Browser
Servlets in Gang setzen (deployen)
1. Servlet Klasse programmieren
• Ableiten von HttpServlet
• doGet, doPost überschreiben
• Evtl. init, destroy überschreiben
• init – wird aufgerufen bei Aktivierung des Servlets (z.B. bei
erstem Ansprechen des Servlets – hängt von Webserver
Konfiguration ab)
• destroy – Aufruf z.B. bei Herunterfahren des Webservers
HTTP Anfrage lesen
lesen
HTTP Anfrage
import
import java.io.*;
java.io.*;
import
import javax.servlet.*;
javax.servlet.*;
import
javax.servlet.http.*;
import javax.servlet.http.*;
HTTP
HTTPAntwort
Antwort
schreiben
schreiben
public
public class
class HelloWorldServlet
HelloWorldServlet extends
extends HttpServlet
HttpServlet {{
public
public void
void doGet(HttpServletRequest
doGet(HttpServletRequest request,
request,
HttpServletResponse
HttpServletResponse response)
response)
throws
IOException,ServletException
throws IOException,ServletException {{
response.setContentType("text/html");
response.setContentType("text/html");
PrintWriter
im
PrintWriter out
out == response.getWriter();
response.getWriter(); bewirkt
bewirkt
im
out.println("<html>");
out.println("<html>");
HTTP
out.println("<head>");
HTTPheader:
header:
out.println("<head>");
out.println("<title>
Contentout.println("<title> HalloWelt
HalloWelt </title>");
</title>");
Contentout.println("</head>");
out.println("</head>");
Type:text/html
out.println("<body>");
Type:text/html
out.println("<body>");
out.println("<h1>HalloWelt</h1>");
out.println("<h1>HalloWelt</h1>");
out.println("Herzlich
out.println("Herzlich willkommen");
willkommen"); Zugriff
Zugriffauf
aufDatenteil
Datenteil
out.println("</body></html>");
out.println("</body></html>");
von
vonHTTP
HTTPAntwort
Antwort
}}
}}
mit
mitPrintWriter
PrintWriterStrom
Strom
2. Servlet Klasse übersetzen und bei Tomcat Server ablegen z.B. in
C:\Programme\Apache Software Foundation\Tomcat
6.0\webapps\eva\WEB-INF\classes
Übersetzen: servlet-api.jar vom Tomcat Server wird gebraucht!
Aktuelles Verzeichnis: webapps\eva\WEB-INF\classes
javac -classpath
.;C:\Programme\APACHE~1\TOMCAT~1.0\lib\servlet-api.jar
HelloWorldServlet.java
3. Konfiguration auf dem Webserver (Tomcat):
(erst alle <servlet> Einträge, dann alle <servlet-mapping> Einträge)
<?xml
<?xml version="1.0"
version="1.0" encoding="ISO-8859-1"?>
encoding="ISO-8859-1"?>
<web-app
xmlns="http://java.sun.com/xml/ns/j2ee"
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.5">
version="2.5">
<display-name>
Servlet-undJSP-Beispiele
</display-name>
<display-name> Servlet-undJSP-Beispiele </display-name>
<description>
<description> Servlet-undJSP-Beispiele
Servlet-undJSP-Beispiele </description>
</description>
<servlet>
<servlet>
<servlet-name>
<servlet-name> HelloWorld
HelloWorld </servlet-name>
</servlet-name>
<servlet-class>
HelloWorldServlet
<servlet-class> HelloWorldServlet </servlet-class>
</servlet-class>
</servlet>
</servlet>
<servlet-mapping>
<servlet-mapping>
<servlet-name>
<servlet-name> HelloWorld
HelloWorld </servlet-name>
</servlet-name>
<url-pattern>
/HalloWelt
</url-pattern>
<url-pattern> /HalloWelt </url-pattern>
</web-app>
</web-app>
</servlet-mapping>
</servlet-mapping>
eva ist Anwendung in Tomcat
webapps\eva\WEB-INF\classes
http://localhost:8080/eva/HalloWelt
webapps\eva\WEB-INF\classes
ist Startpunkt für Suche nach Java Klassen beim Laden
4. Web Server starten
Änderungen am Servlet werden nicht automatisch übernommen ->
/conf/context.xml ändern: <Context reloadable=„true“>
-> Auf altem Servlet destroy, auf neuem init
String: Wert des
Eingabefelds
null, wenn
Name des
Eingabefelds
nicht existent
Rückgabe
Zugriff auf Formulardaten
<HttpRequest Objekt>.getParameter
(String <Name des Formular Eingabefelds>)
Servlets in Aktion
HTML Page
Servlet
im Source Code:
Servlet
compilieren
(Wenn Tomcat schon läuft – Reload
im Manager nicht vergessen!)
Start Tomcat
Start HTML
http://localhost:8080/eva/Tea.html
Parallelität bei Servlets
•Alle HTTP Anfragen laufen in eigenen Threads
•Lesende und schreibende Zugriffe auf gemeinsame Resourcen daher
synchronized
Anwendungsglobale Daten
•Zugriff verschiedener Servlets derselben Anwendung auf
gemeinsame Objekte
•HttpServlet vererbt getServletContext
•Ein ServletContext beinhaltet Attributliste (getAttribute(String))
Interface ServletContext
•servlet communication with its servlet container,
•e.g get the MIME type of a file, dispatch requests, write to a
log file.
•There is one context per "web application" per Java Virtual
Machine.
•"web application“ = collection of servlets and content installed
under a specific subset of the server's URL namespace
Beispiel: Zähler
•Ein Servlet für increment und eins für reset
•gemeinsame Resource Counter – Objekt:
public
public class
class Counter
Counter {{
private
private int
int counter;
counter;
public
public synchronized
synchronized int
int increment()
increment() {{
counter++;
counter++;
return
return counter;
counter;
}}
public
public synchronized
synchronized int
int reset()
reset() {{
counter
counter == 0;
0;
return
counter;
return counter;
}}
}}
•ResetServlet trägt Counter in Attributliste von ServletContext ein
•Problem: wenn IncrementServlet zuerst aufgerufen wird!
•Lösung: Änderung von Konfigurationsdatei
<servlet>
<servlet>
<servlet-name>Reset</servlet-name>
<servlet-name>Reset</servlet-name>
<servlet-class>RestServlet</servlet-class>
<servlet-class>RestServlet</servlet-class>
<load-on-startup>
<load-on-startup>
<servlet>
Erzeugung Servlet Objekt
<servlet>
Erzeugung Servlet Objektbereits
bereitsbei
bei
Installieren
Installierenbzw.
bzw.Neuladen
Neuladender
derAnwendung
Anwendung
ResetServlet
public
public void
void init()
init() {{
Counter
Counter counter
counter == new
new Counter();
Counter();
ServletContext
ServletContext ctx
ctx == getServletContext();
getServletContext();
ctx.setAttribute("Counter",
ctx.setAttribute("Counter", counter);
counter);
}}
public
public void
void doPost(HttpServletRequest
doPost(HttpServletRequest request,
request,
HttpServletResponse
HttpServletResponse response)
response)
throws
throws IOException,
IOException, ServletException
ServletException {{
...
...
ServletContext
ServletContext ctx
ctx == getServletContext();
getServletContext();
Counter
counter
=
(Counter)
Counter counter = (Counter) ctx.getAttribute("Counter");
ctx.getAttribute("Counter");
int
value
=
counter.reset();
int value = counter.reset();
out.println("Der
out.println("Der Zähler
Zähler wurde
wurde auf
auf "" ++ value
value
++ "" zurückgesetzt.<p>");
zurückgesetzt.<p>");
...
...
}}
Herunterladen