PARALLELE S YSTEME Ü BUNG 9, 18.06.08 DAS P HILOSOPHENPROBLEM Ü BERBLICK : ➜ Deadlocks de 1 Slide 3 ➜ Philosophenproblem ➜ 5 Philosophen speisen mit 5 Gabeln am runden Tisch, wobei jeder zum Essen 2 Gabeln braucht. Ein Philosoph kommt zu einem zufälligen Zeitpunkt an seinen Platz, nimmt zwei Gabeln und ißt zufällig lange. ➜ Eine Lösung des Philosophen Problems besteht darin, dass ein Butler nur maximal vier Philosophen gestattet, am Tisch gleichzeitig Platz zu nehmen. Ein Philosoph muss vor dem Essen erst Platz nehmen, und wenn er fertig ist, steht er auf. ➜ Lösung mit Butler (Semaphor ) ➜ Thread-Synchronisation und Java-Performance ➜ Deadlocks automatisch verhindern mit dem DeadlockDetectingLock C OFFMAN , E LPHICK & S HOSHANI : D EADLOCK B EDINGUNGEN B UTLER Vier Bedingungen: de 2 JAVA ➜ Butler entspricht einem Semaphor ➀ Serielle Benutzung von Resourcen (gegenseitiger Ausschluss) Z.B.: Monitore mit synchronized Methoden ➁ Inkrementelles Resourcen-Erlangen, d.h. während des Wartens auf eine Resource gibt der Prozeß die bereits erlangten Resourcen nicht frei Z.B.: Bei verschachtelten Monitoren behält Consumer den Lock, während er auf den Producer wartet ➂ Keine Preemption, d.h. dem Prozeß kann die bereits erlangte Resource nicht genommen werden Z.B.: Wird dem Consumer der Lock genommen, kann der Producer weiter arbeiten und somit gibt es kein Deadlock ➃ Wartezyklus: eine Prozeßkette existiert, wo jeder Prozeß die Resource hält, auf die sein Vorgänger wartet Z.B.: Consumer hält den Lock, den Producer benötigt; Producer hält das neue Symbol, den Consumer benötigt DAS P HILOSOPHENPROBLEM IN Slide 4 1 class Butler { private int value; public Butler(int i) { value=i; } synchronized public void sitdown() { while(value==0) try { wait(); } catch(InterruptedException e) {} --value; } synchronized public void arise() { ++value; notify(); } } F ORK . JAVA – M UTEX 2 F ORK . JAVA – M UTEX P HILT HREAD . JAVA – KONSTANTEN , VARIABLEN , KONSTRUKTOR class Fork { private boolean inUse=false; private int number; private PhilPanel philPanel; class PhilThread extends Thread { public static int AWAY=0; public static int HUNGRY=1; public static int GOTRIGHT=2; public static int EATING=3; public static int SITTING=4; public Fork(PhilPanel p, int n) { number=n; philPanel=p; } public synchronized void get() { while(inUse) try { wait(); } catch(InterruptedException e) {} inUse=true; EventQueue.invokeLater(new Runnable() { public void run() { philPanel.drawFork(philPanel.getGraphics(), false, number);}}); } ide 5 private Fork left, right; Slide 7 private Butler butler; private PhilPanel philPanel; private int number; public PhilThread(Fork l, Fork r, Butler b, PhilPanel p, int n) { left=l; right=r; butler=b; philPanel=p; number=n; } ... P HILT HREAD . JAVA – RUN () ➜ Zwischen allen Aktionen natürlich noch ein entsprechendes sleep() private void draw(int state) { public synchronized void put() { EventQueue.invokeLater(new Runnable() { inUse=false; public void run() { EventQueue.invokeLater(new Runnable() { philPanel.drawPhil(philPanel.getGraphics(), public void run() { philPanel.drawFork(philPanel.getGraphics(), de 6 state, number);}}); Slide 8 true, number);}}); } public void run() { while(true) { notify(); draw(AWAY); } butler.sitdown(); draw(HUNGRY); } right.get(); draw(GOTRIGHT); left.get(); draw(EATING); left.put(); draw(GOTRIGHT); right.put(); draw(SITTING); butler.arise(); P HILT HREAD. JAVA – K ONSTANTEN , VARIABLEN , K ONSTRUKTOR 3 } } P HIL PANEL . JAVA – VARIABLEN , PAINT C OMPONENT, K ONSTRUKTOR 4 P HIL PANEL . JAVA – VARIABLEN , PAINT C OMPONENT, P HIL PANEL . JAVA – KONSTRUKTOR DRAW F ORK public void drawFork(Graphics g,boolean state, private final static int N=5; private Fork[] forks=new Fork[N]; int fork) { private PhilThread[] phils=new PhilThread[N]; int x=0, y=0; forkState[fork]=state; private int[] philState=new int[N]; switch(fork) { private boolean[] forkState=new boolean[N]; case 0: x=170; y=100; break; private Butler butler; case 1: x=200; y=120; break; private Image[] imPhil=new Image[5]; Slide 11 de 9 public void paintComponent(Graphics g) { case 2: x=190; y=160; break; case 3: x=150; y=160; break; super.paintComponent(g); case 4: x=140; y=120; break; g.drawOval(125, 90, 100, 100); if(g!=null) { for(int i=0; i<N; i++) { } if(state==true) { drawPhil(g, philState[i], i); g.setColor(Color.black); drawFork(g, forkState[i], i); g.fillOval(x,y,10,10); } } else g.clearRect(x,y,10,10); } P HIL PANEL . JAVA – public PhilPanel( ) { }} DRAW P HIL imPhil[PhilThread.EATING]=Toolkit. public void drawPhil(Graphics g, int state, getDefaultToolkit().getImage("eating.gif"); int phil) { imPhil[PhilThread.GOTRIGHT]=Toolkit. int x=0, y=0; philState[phil]=state; getDefaultToolkit().getImage("gotright.gif"); imPhil[PhilThread.HUNGRY]=Toolkit. switch (phil) { getDefaultToolkit().getImage("hungry.gif"); case 0: x=50; y=0; break; imPhil[PhilThread.SITTING]=Toolkit. case 1: x=200; y=0; break; getDefaultToolkit().getImage("thinking.gif"); e 10 Slide 12 butler=new Butler(N-1); case 2: x=250; y=100; break; case 3: x=125; y=200; break; for(int i=0; i<N; i++) { forks[i]=new Fork(this, i); case 4: x=0; y=100; break; forkState[i]=true; } } for(int i=0; i<N; i++) { if(g!=null) { philState[i]=PhilThread.AWAY; g.clearRect(x,y,100,90); phils[i]=new PhilThread(forks[i], forks[((i-1)+N)%N], butler, this, i); phils[i].start( ); P HIL PANEL . JAVA – DRAW F ORK } if(state!=PhilThread.AWAY) g.drawImage(imPhil[state], x, y, null); }} } 5 S YNCHRONISIATION UND P ERFORMANCE 6 S YNCHRONIZED -W RAPPERS IN DEN C OLLECTION API S Beispiel: S YNCHRONISIATION UND List myUnsaveList = new ArrayList(CAPACITY); List mySaveList = Collections.synchronizedList(myUnsaveList); P ERFORMANCE // Alternative: Collections.unmodifiableList ➜ Übermäßige Synchronisation erweist sich oft als Flaschenhals“ ” ➜ Beim Entwurf einer Klasse lässt sich jedoch selten vorhersehen, ob Instanzen von mehreren Threads zugleich genutzt werden e 13 parallelMutator.execute(mySaveList); Slide 15 ➜ Um auf der sicheren Seite zu sein, sind Modifikatormethoden zustandsbehafteter Objekte, i.a. synchronisiert Performance-Messungen: JVM 1.2 JVM 1.4 JVM 1.4 -Xint Vector 100% 20% 134% ArrayList 15% 14% 166% Wrapped ArrayList 131% 26% 231% ➜ Ein gängiger Trick“ Flaschenhälse zu verhindern, ist die ” Verwendung sog. synchronized-wrapper ➜ Quelle: Java Performance Tuning“, Jack Shirazi, O’Reilly 2003 ” S YNCHRONIZED -W RAPPERS ➜ Synchronized-Wrappers ermöglichen selektiv unsynchronisierten Zugriff auf Modifikatoren S YNCHRONISATION public void add(int aNumber); } e 14 public class UnsyncedAdder implements Adder int total, numAdditions; public void add(int aNumber) total += aNumber; GEZIELT VERWENDEN ➜ Beachte: Jede einzelne Akquisition eines Schlosses kann den Anwendungsfluss erheblich drosseln ➜ Beispiel: fetch-emthode einer ungeschickten Cache-Implementierung: public interface Adder { { Slide 16 { public Object fetch(String key) { if(! myHashtable.containsKey(key)) { numAdditions++; myHashtable.put(key, createNewEntry(key)); } } } public class SyncedAdder implements Adder return myHashtable.get(key); { } Adder adder; public SyncedAdder(Adder a) { adder = a; } public synchronized void add(int aNumber) { adder.add(aNumber); } } S YNCHRONIZED -W RAPPERS IN DEN C OLLECTION API S 7 T HREAD -C ONTENTION VERHINDERN 8 T HREAD -C ONTENTION VERHINDERN ➜ Die Implementierung des Caches, lässt sich optimieren, indem eine unsynchronisierte HashMap verwendet wird ➜ Beachte: In diesem Fall ist die fetch-Methode zu synchronisieren! D EADLOCK D ETECTING L OCK zyklische Definition einer Verklemmung (Deadlock): Ein Schloss mit Verklemmung ist ein Schloss, das auf public synchonized Object fetch(String key) { einen Thread mit Verklemmung wartet. if(! myHashMap.containsKey(key)) { Ein Thread mit Verklemmung ist ein Thread, der auf ein myHashMap.put(key, createNewEntry(key)); } e 17 Slide 19 return myHashMap.get(key); } Schloss mit Verklemmung wartet. • Technisch gesehen, kann ein Schloss keinen Thread besitzen: aktiv ist nur der Thread, der auf ein Schloss wartet ➜ Ein Flaschenhals aufgrund ungeschickter Synchronisation wird auch als Thread-Contention Problem bezeichnet ➜ Thread-Contention Probleme lassen sich mit Profilierungs-Tools aufspühren. • Wir unterscheiden nicht bei Beziehungen zwischen Schlössern und Threads ➜ siehe z.B.: http://freshmeat.net/projects/contentionprofiler D EADLOCKFREIE S PERREN V ERWALTUNG ➜ Ein Thread der fortwährend auf ein Schloß wartet, wird als hard-waiting klassifiziert (analog: soft-waiting mit timeout) S CHL ÖSSER UND T HREADS 2 Listen: Schlösser, die ein Thread hält ➜ Jedes Schloß, das ein Thread in Besitz nimmt wird registriert Alle Schlösser, die ein Thread hält, sind in der DeadlockLockRegistry enthalten ➜ Weiterhin speichern wir zu jedem Schloss alle hard-waiting-Threads, so daß ein Wartebaum entsteht e 18 DER ➜ Bevor ein Thread eine neue hard-waiting-Operation auf einem Schloß durchgeführen kann, prüfen wir in einem Tiefendurchlauf, ob das Schloß bereits im Wartebaum dieses Threads enthalten ist und ggf. eine DeadlockDetectedException) ausgelöst ➜ Ein circular-wait ist somit ausgeschlossen DeadlockLockRegistry Slide 20 Threads, die auf ein Schloß warten Zu jedem Schloss gibt es eine Liste aller zugehörigen hardwaitingThreads Quelle: http://www.onjava.com (O’Reilly online) hardwaitingThreads public class DeadlockDetectedException extends RuntimeException { public DeadlockDetectedException (String s) { ➜ Ein Thread besitzt ein Schloss, das wartende Threads hat, die Schlösser haben, auf die andere Threads warten usw. super(s); } ➜ Durch Beziehungen zwischen Schlössern und Threads wird ein Baum beschrieben: der Wartebaum } D EADLOCK D ETECTING L OCK 9 S CHLOSSSUCHE IM WARTEBAUM 10 S CHLOSSSUCHE IM D EADLOCK D ETECTING L OCK , WAR TEBAUM private List hardwaitingThreads = new ArrayList( ); Prinzip: Deadlocks automatisch verhindern private static synchronized void markAsHardwait(List l, Thread t) { Wartebaum if (!l.contains(t)) l.add(t); } private static synchronized void freeIfHardwait(List l, Thread t) { Ein Deadlock tritt auf, wenn ein Thread auf ein Schloss wartet, das in seinem if (l.contains(t)) l.remove (t); } Wartebaum enthalten ist. private static Iterator getAllLocksOwned (Thread t) { DeadlockDetectingLock current; ArrayList results = new ArrayList( ); Bevor ein Thread beginnt auf ein neues Schloss zu warten, prüfen wir, dass e 21 Slide 23 Iterator itr = deadlockLocksRegistry.iterator( ); dieses Schloss nicht im Wartebaum while (itr.hasNext( )) { current = (DeadlockDetectingLock)itr.next( ); if (current.getOwner( ) == t) gespeichert ist (rekursive Tiefensuche) results.add(current); canThreadwaitOnLock } ➜ Die die Lock Klassen in Java 5 ermöglichen die Definition von Sperren, bei denen eine Solche Suche implizit durchgeführt wird return results.iterator( ); } private static Iterator getAllThreadsHardwaiting(DeadlockDetectingLock l) { ➜ ReentrantLock wird erweitert und lock, lockInterruptibly und tryLock werden überschrieben D EADLOCK D ETECTING L OCK IN return l.hardwaitingThreads.iterator( ); } D EADLOCK D ETECTING L OCK , JAVA CONT. private static synchronized boolean import java.util .*; import java.util.concurrent .*; import java.util.concurrent .locks .*; canThreadWaitOnLock(Thread t, DeadlockDetectingLock l) { Iterator locksOwned = getAllLocksOwned(t); while (locksOwned.hasNext( )) { DeadlockDetectingLock current = public class DeadlockDetectingLock extends ReentrantLock { (DeadlockDetectingLock)locksOwned.next( ); if (current == l) private static List deadlockLocksRegistry = new ArrayList( ); e 22 CONT. private static synchronized void registerLock(DeadlockDetectingLock ddl) { return false; Slide 24 Iterator waitingThreads = getAllThreadsHardwaiting(current); if (!deadlockLocksRegistry.contains(ddl)) while (waitingThreads .hasNext( )) { Thread otherthread = (Thread)waitingThreads.next( ); deadlockLocksRegistry.add(ddl); } if (!canThreadWaitOnLock(otherthread, l)) { return false; private static synchronized void unregisterLock(DeadlockDetectingLock ddl) { } if ( deadlockLocksRegistry.contains(ddl)) } deadlockLocksRegistry.remove(ddl ); return true; } D EADLOCK D ETECTING L OCK , CONT. } 11 D EADLOCK D ETECTING L OCK , CONT. 12 D EADLOCK D ETECTING L OCK , CONT. public DeadlockDetectingLock ( ) { this(false, false); } public DeadlockDetectingLock (boolean fair) { this(fair , false); } private boolean debugging ; public DeadlockDetectingLock (boolean fair , boolean debug) { super(fair); debugging = debug ; registerLock (this); } public void lock( ) { if (isHeldByCurrentThread( )) { if(debugging) System.out.println("already own lock"); e 25 super.lock( ); freeIfHardwait(hardwaitingThreads, Thread.currentThread( )); return; } markAsHardwait(hardwaitingThreads , Thread.currentThread( )); if (canThreadWaitOnLock(Thread.currentThread( ), this)) { if (debugging ) System.out.println("waiting for lock"); super.lock( ); freeIfHardwait(hardwaitingThreads , Thread.currentThread( )); if (debugging) System.out.println ("got new lock"); } else { throw new DeadlockDetectedException("DEADLOCK"); }} D EADLOCK D ETECTING L OCK , CONT. ➜ Die Methode sumArray hat bei ungünstiger Threadkoordination einen Deadlock zur Folge: public int sumArrays(int [ ] a1, int [ ] a2) { int value = 0; int size = a1.length; if (size == a2.length) { e 26 synchronized(a1) { synchronized(a2) { for (int i = 0; i < size; ++i) value += a1 [ i ] + a2 [ i ]; } } } return value; } D EADLOCK D ETECTING L OCK , CONT. 13