1 - Zweielementiger Buffer in Java Teil a) 1. In der Methode take wird eine if- statt einer while-Anweisung verwendet. Dadurch kann es bei einem leeren Puffer dazu kommen dass ein Leser t1 zunächst wartet. Wird nun ein Wert geschrieben, könnten sich T1 und ein neuer Leser t2 um den r-Lock bewerben. Erhält zunächst t2 den Lock, kann erst t2 lesen bevor dann t1 den Lock erhält, nicht erneut überprüft ob der Puffer leer ist, und nur null liest. 2. Analog hierzu kann es uum Überschreiben eines Wertes kommen, da in der Methode put eine if- statt einer while-Anweisung verwendet wird. Ist der Puffer voll, so wartet ein Schreiber p1 zunächst. Wir ein Wert gelesen, könnten sich p1 und ein neuer Schreiber p2 um den w-Lock bewerben. Erhält zunächst p2 den Lock, kann erst p2 schreiben bevor dann p1 den Lock erhält, nicht erneut überprüft ob der Puffer voll ist, und so den Wert von p2 überschreibt. 3. Zu einem Deadlock kann es bei einem halbvollen Puffer kommen, wenn gleichzeitig ein Schreiber und ein Leser den Puffer nutzen wollen. Der Schreiber sichert sich dann den w-Lock, der Leser den r-Lock. Da der Puffer weder voll noch leer ist warten beide dann jeweils auf den anderen Lock. Teil b) 1 package solution; 2 3 4 /** Two element buffer with synchronization. */ public class Buffer2<E> { 5 6 7 8 // element + empty flag private E content, content2; private boolean empty, full; 9 10 11 // synchronization object private Object lock = new Object(); 12 13 14 15 16 public Buffer2() { empty = true; full = false; } 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 /** * take the element from the buffer; suspends on an empty buffer. * * @return element of the buffer * @throws InterruptedException */ public E take() throws InterruptedException { synchronized (lock) { while (empty) { lock.wait(); } E res = content; content = content2; content2 = null; empty = !full; full = false; lock.notifyAll(); return res; } 1 } 37 38 /** * put an element into the buffer; suspends on a full buffer * * @param o * Object to put into * @throws InterruptedException */ public void put(E o) throws InterruptedException { synchronized (lock) { while (full) { lock.wait(); } if (empty) { content = o; } else { content2 = o; full = true; } empty = false; lock.notify(); } } 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 /** * Return whether the buffer is empty * * @return true if empty */ public boolean isEmpty() { return empty; } 62 63 64 65 66 67 68 69 70 /** * Return whether the buffer is empty * * @return true if empty */ public boolean isFull() { return full; } 71 72 73 74 75 76 77 78 79 80 } 2 - Synchronisation in Java Teil a) Bei einem Programmablauf mit 2 Threads wäre folgendes Scheduling möglich: T1: T2: T1: T2: sysout "flip" sysout "flip" state = true; state = false; 2 T1: T2: T1: T2: ... sysout "flip" sysout "flip" state = true; state = false; Hierzu kommt es zu mehreren Zustandswechseln, ohne dass die Ausgabe flop erscheint. Teil b) Bei der Client-Side-Synchronization synchronisieren sich die Clients mit dem Lock-Objekt des Servers: // Client FlipServer s = new FlipServer(); ... synchronized (s) { s.flip(); } Bei der Server-Side-Synchronization ist die Methode flip des Servers selbst synchronisiert: // Server public synchronized void flip () { ... } Teil c) Bei RMI ist es grundsätzlich so, dass die Clients von der RMI-Registry nicht das Serverobjekt selbst erhalten, sondern nur ein lokales Stellvertreterobjekt. Da die Clients sich prinzipiell auch auf verschiedenen Rechnern befinden können erhält jeder Client auch sein eigenes Stellvertreterobjekt. Daher ist eine Client-Side-Synchronization nicht möglich, da die verschiedenen Stellvertreterobjekte nichts voneinder wissen (können). Die Server-Side-Synchronization ist mit RMI jedoch möglich, da das Serverobjekt ja nur ein einziges Mal existiert und somit auch der Lock eindeutig ist. 3 - MergeSort in Haskell 1 2 3 4 5 6 mergesort :: Ord a => [a] -> [a] mergesort = sort . map (:[]) where sort [] = [] sort [xs] = xs sort xss = sort (mergePairs xss) 7 8 9 10 mergePairs :: Ord a => [[a]] -> [[a]] mergePairs (xs:ys:zss) = merge xs ys : mergePairs zss mergePairs xss = xss 11 12 13 14 15 16 merge merge merge merge :: Ord a => [a] -> [a] -> [a] [] ys xs [] xs@(x:xs1) ys@(y:ys1) | x <= y | otherwise = = = = ys xs x : merge xs1 ys y : merge xs ys1 3 4 - IO in Haskell 1 import System.Random (randomRIO) 2 3 4 5 6 7 8 9 readNumber :: IO Int readNumber = do putStr "Ihre Zahl: " str <- getLine case reads str of [(n, [])] -> return n _ -> putStrLn "Das ist keine Zahl." >> readNumber 10 11 12 randomNumber :: Int -> IO Int randomNumber n = randomRIO (1, n) 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 guess :: Int -> IO () guess maxVal = do n <- randomNumber maxVal putStrLn $ "Willkommen zum Raten einer Zahl zwischen 1 und " ++ show maxVal play 1 n where play try n = do m <- readNumber case compare m n of LT -> do putStrLn "Zu klein." play (try + 1) n EQ -> putStrLn $ "Hurra, " ++ show try ++ " Versuch(e)." GT -> do putStrLn "Zu groß." play (try + 1) n 5 - Peano-Zahlen in Prolog 1 2 fromPeano(o , 0). fromPeano(s(P), N) :- fromPeano(P, M), N is M + 1. 3 4 5 toPeano(0, o ) . toPeano(N, s(P)) :- N > 0, N1 is N - 1, toPeano(N1, P). 6 7 8 peano(N, P) :- var(N), !, fromPeano(P, N). peano(N, P) :- toPeano(N, P). 9 10 11 % Alternativ: % peano(N, P) :- var(N) -> fromPeano(P,N) ; toPeano(N,P). Wenn beide Argumente von peano freie Variablen sind wird fromPeano verwendet, dass nacheinander alle Paare von Peanozahl und natürlicher Zahl aufzählt. Dies ist bereits ein nützliches Verhalten, das wir nicht weiter verbessern können. 6 - Verwandtschaftsbeispiel in Prolog Folgender Code war vorgegeben: 4 1 2 3 4 5 6 % eltern(Mutter, eltern(erika , eltern(erika , eltern(karin , eltern(britta , eltern(elfriede, Vater, Kind) achim , karl ). achim , andreas ). karl , gustav ). karl , elfriede). gustav, dagmar ). 7 8 9 10 11 % maennlich(Mann) maennlich(andreas). maennlich(gustav ). maennlich(M ) :- eltern(_, M, _). 12 13 14 % grossvater(Grossvater,Enkel) grossvater(G,E) :- eltern(_, G, K), (eltern(K, _, E) ; eltern(_, K, E)). 15 16 17 % vollgeschwister(Geschwister1, Geschwister2) vollgeschwister(G1, G2) :- eltern(M, V, G1), eltern(M, V, G2), G1 \= G2. 18 19 20 % bruder(Kind, Bruder) bruder(K, B) :- vollgeschwister(K, B), maennlich(B). Teil a) Zunächst nummerieren wir alle Regeln der Prädikate, die wir zum Beweis der Anfrage benötigen, durch: (1) (2) (3) (4) (5) eltern(erika , eltern(erika , eltern(karin , eltern(britta , eltern(elfriede, achim , achim , karl , karl , gustav, karl ). andreas ). gustav ). elfriede). dagmar ). (6) maennlich(andreas). (7) maennlich(gustav ). (8) maennlich(M ) :- eltern(_, M, _). (9) vollgeschwister(G1, G2) :- eltern(M, V, G1), eltern(M, V, G2), G1 \= G2. (10) bruder(K, B) :- vollgeschwister(K, B), maennlich(B). Und nun die Ableitung als Baum: ?- bruder(andreas, B). | | Regel (10) | { K1 -> andreas, B -> B1 } | ?- vollgeschwister(andreas, B1), maennlich(B1). | | Regel (9) | { G11 -> andreas, G21 -> B1 } | ?- eltern(M1, V1, andreas), eltern(M1, V1, B1), andreas \= B1, maennlich(B1). | | Regel (2) | { M1 -> erika, V1 -> achim } 5 | ?- eltern(erika, achim, B1), andreas \= B1, maennlich(B1). / \ / Regel (1) \ Regel (2) / { B1 -> karl } \ { B1 -> andreas } / \ ?- andreas \= karl, maennlich(karl). ?- andreas \= andreas, maennlich(andreas). | % Fehlschlag | ohne Regel | { } | ?- maennlich(karl). | | Regel (8) | { X1 -> karl } | ?- eltern(_, karl, _). / \ / Regel (3) \ Regel (4) / { } \ { } / \ ?-. ?-. % Erfolg mit B = karl % Erfolg mit B = karl Berechnung der Lösung nach dem Suchverfahren von Prolog: ?- bruder(andreas, B). |- { K1 -> andreas, B -> B1 } Regel (10) ?- vollgeschwister(andreas, B1), maennlich(B1). |- { G11 -> andreas, G21 -> B1 } Regel (9) ?- eltern(M1, V1, andreas), eltern(M1, V1, B1), andreas \= B1, maennlich(B1). |- { M1 -> erika, V1 -> achim } Regel (2) ?- eltern(erika, achim, B1), andreas \= B1, maennlich(B1). (*) |- { B1 -> karl } Regel (1) ?- andreas \= karl, maennlich(karl). |- { } ohne Regel ?- maennlich(karl). |- { X1 -> karl } Regel (8) ?- eltern(_, karl, _). (**) |- { } Regel (3) ?-. Ausgabe: B = karl Rücksetzen: Regel (4) bei (**) |- { } ?-. Ausgabe: B = karl Rücksetzen: Regel (2) bei (*) |- { B1 -> andreas } ?- andreas \= andreas, maennlich(andreas). Fehlschlag Teil b) Das Problem kann man beheben, indem man wie folgt einen Cut verwendet: 21 22 % maennlich2(Mann) maennlich2(andreas) :- !. 6 23 24 maennlich2(gustav) maennlich2(M) :- !. :- eltern(_, M, _), !. Der Cut in der ersten Zeile ist nicht zwingend notwendig, da andreas kein Kind hat, aber vielleicht ändert sich das ja noch. Allerdings ist diese Lösung nicht sinnvoll, wenn man maennlich2 mit einer freien Variablen aufruft, da dann nur ein Mann (in diesem Fall andreas) geraten wird. Teil c) 25 26 halbgeschwister(G1, G2) :- eltern(M1, V, G1), eltern(M2, V, G2), M1 \= M2, G1 \= G2. halbgeschwister(G1, G2) :- eltern(M, V1, G1), eltern(M, V2, G2), V1 \= V2, G1 \= G2. Teil d) 27 kinderVon(P, L) :- findall(K, eltern(P, _, K) ; eltern(_, P, K), L). 7