H 16. MULTITHREADING. 1. INLEIDING. Threads: delen van het programma die in concurrentie met elkaar gelijktijdig in executie gaan. Thread is een sequentiële besturingsstroom. Het zijn ‘lichtgewicht’ processen. Java voorziet primitieven voor multithreading. Meeste programmeertalen moeten gebruik maken van OS-primitieven voor multithreading en dus platformspecifieke code gebruiken. De werking van Java’s thread scheduling is wel platform afhankelijk. Een voorbeeld van multithreading is Java’s garbage collector. JAVA 1 2. THREAD TOESTANDEN: LEVENSCYCLUS. Born start quantum expiration yield Waiting sleep interval expires interrupt thread dispatch (assign a processor) I/O completes acquire lock interrupt notify notifyAll timeout expires interrupt Ready Running Sleeping Blocked Bij voltooing van een thread (terugkeer van run methode), wordt de Dead toestand bereikt (hier de final state) JAVA 2 2. THREAD TOESTANDEN: LEVENSCYCLUS. Thread toestanden. Born state Ready state (ook Runnable state) Thread is juist gecreëerd. start method van thread geactiveerd. Thread kan nu in executie. Running state Thread is toegekend aan processor en in executie. Dispatching the thread. Dead state Thread is beëindigd (taak volbracht of exit) Garbage collector kan geheugen terug vrijgeven. JAVA 3 2. THREAD TOESTANDEN: LEVENSCYCLUS. Blocked state Waiting state Als een taak niet onmiddellijk kan volbracht worden (vb. i/o verzoek). Als een deel code nog niet kan uitgevoerd worden (bepaalde vereisten moeten voldaan zijn) thread activeerd Object’s wait methode. Sleeping state Thread wacht tot slaaptijd is verstreken. JAVA 4 3. THREAD PRIORITIES EN SCHEDULING. Priority: indicatie van belangrijkheid (voorrang voor processortijd). Thread.MIN_PRIORITY (0) .. Thread.NORM_PRIORITY (5) .. Thread.MAX_PRIORITY (10). Thread scheduler: zorgt ervoor dat de thread met hoogste prioriteit in Running state verkeert. Bij meerdere threads met gelijke prioriteit wordt timeslicing gebruikt. Elke thread krijgt een ‘quantum’ processortijd toegewezen. Niet elk Java platform ondersteund timeslicing. De Thread methode yield kan er dan voor zorgen dat threads van gelijke prioiteit kunnen concurreren. JAVA 5 3. THREAD PRIORITIES EN SCHEDULING. Ready threads Thread.MAX_PRIORITY Priority 10 A Priority 9 C B Priority 8 Thread.NORM_PRIORITY Priority 7 D E Priority 6 G Priority 5 H I J K F Priority 4 Priority 3 Priority 2 Thread.MIN_PRIORITY Priority 1 JAVA 6 4. CREATIE EN EXECUTIE VAN THREADS. Demonstratie van sleep methode. Creatie van 3 threads met default prioriteit die op willekeurig tijdsinterval een bericht tonen. // class PrintThread bestuurt de thread executie class PrintThread extends Thread { private int sleepTime; public PrintThread( String name ) { // geef de thread een naam super( name ); // kies een willekeurige slaaptijd tussen 0 and 5 seconde sleepTime = ( int ) ( Math.random() * 5001 ); } JAVA 7 4. CREATIE EN EXECUTIE VAN THREADS. // method run wordt automatisch geactiveerd bij een nieuwe thread public void run() { // breng thread in sleep toestand voor sleepTime seconde try { System.err.println( getName() + " going to sleep for " + sleepTime ); Thread.sleep( sleepTime ); } // als de thread gedurende de sleep toestand wordt onderbroken, print stack trace catch ( InterruptedException exception ) { exception.printStackTrace(); } // print thread name System.err.println( getName() + " done sleeping" ); } } // einde class PrintThread JAVA 8 4. CREATIE EN EXECUTIE VAN THREADS. // Meerdere threads die op verschillende tijdsintervallen printen. public class ThreadTester { public static void main( String [] args ) { // creatie van de drie threads PrintThread thread1 = new PrintThread( "thread1" ); //thread1 in Born toestand PrintThread thread2 = new PrintThread( "thread2" ); //thread1 in Born toestand PrintThread thread3 = new PrintThread( "thread3" ); //thread1 in Born toestand System.err.println( "Starting threads" ); thread1.start(); // Plaats thread1 in Ready toestand thread2.start(); // Plaats thread2 in Ready toestand thread3.start(); // Plaats thread3 in Ready toestand System.err.println( "Threads started, main ends\n" ); } } JAVA 9 4. CREATIE EN EXECUTIE VAN THREADS. Als een thread voor de eerste keer in de Running toestand komt wordt de methode run geactiveerd. Ook al is de maincode beëindigd, het programma eindigt pas als alle threads de Dead toestand bereikt hebben. Starting threads Threads started, main ends thread1 thread2 thread3 thread3 thread1 thread2 going to sleep for 1217 going to sleep for 3989 going to sleep for 662 done sleeping done sleeping done sleeping Starting threads thread1 going to sleep for 314 thread2 going to sleep for 1990 Threads started, main ends thread3 thread1 thread2 thread3 JAVA going to sleep for 3016 done sleeping done sleeping done sleeping 10 5. THREAD SYNCHRONISATIE. Meerdere threads kunnen een object delen, ‘shared object’. Wanneer meerdere threads het shared object kunnen wijzigen kunnen er problemen ontstaan. mutual exclusion of thread synchronisatie. in Java gebruik van monitors voor synchronisatie. Elk object heeft een monitor: geeft maar één thread tegelijkertijd executierecht bij een synchronized statement op het object, ‘obtaining the lock’. Andere threads komen in Blocked toestand. Als de lock wordt vrijgegeven, ‘released’, zal de monitor de geblokte thread met hoogste priority laten voortgaan. Een volledige methode kan synchronized zijn. Deadlock preventie: als een thread de wait methode activeert, zorg dan dat een afzonderlijke thread de notify methode activeerd voor overgang naar de Ready toestand. JAVA 11 6. PRODUCER/CONSUMER RELATIE ZONDER SYNCHRONISATIE. Producer: genereert data en bewaart het in een shared object. Consumer: leest de data van het shared object. In multithreaded applicatie: Componenten: producerthread, consumerthread en buffer. Producerthread: Activeer wait: als vorige data nog niet verwerkt is, consumer kan data ophalen. Activeer notify: als nieuwe data in buffer geplaatst is. Consumerthread: Activeer notify: als de data opgehaald is, producer kan nieuwe data in buffer plaatsen. Activeer wait: als buffer leeg is of vorige data nog aanwezig is. JAVA 12 6. PRODUCER/CONSUMER RELATIE ZONDER SYNCHRONISATIE. Mogelijke logische fouten als we geen synchronisatie gebruiken: VOORBEELD SharedBufferTest. Producer genereert een reeks getallen van 1 tot 4. Consumer telt het aantal getallen en sommeert die getallen. Bij beide threads is een random vertraging ingebouwd (0 tot 3 seconden sleep) om een reëel programma te simuleren. Bij multithreaded applicaties is het ongekend wanneer een thread zijn taak uitvoert en hoelang dat duurt. Beide threads drukken ook de getallen af. Componenten: Interface Buffer en Klassen Producer, Consumer, UnsynchronizedBuffer en SharedBufferTest. JAVA 13 6. PRODUCER/CONSUMER RELATIE ZONDER SYNCHRONISATIE. public interface Buffer { public void set( int value ); // plaats een waarde in de buffer public int get(); // haal een waarde uit de buffer } public class Producer extends Thread { private Buffer sharedLocation; // reference naar shared object public Producer( Buffer shared ) { super( "Producer" ); sharedLocation = shared; } JAVA 14 6. PRODUCER/CONSUMER RELATIE ZONDER SYNCHRONISATIE. public void run() // berg waarden 1 tot 4 op in sharedLocation { for ( int count = 1; count <= 4; count++ ) { try { // sleep 0 to 3 seconde en plaats waarde in buffer Thread.sleep( ( int ) ( Math.random() * 3001 ) ); sharedLocation.set( count ); } // indien sleeping thread interrupted druk stack trace catch ( InterruptedException exception ) { exception.printStackTrace(); } } System.err.println( getName() + " done producing." + "\nTerminating " + getName() + "."); } } // einde class Producer JAVA 15 6. PRODUCER/CONSUMER RELATIE ZONDER SYNCHRONISATIE. public class Consumer extends Thread { private Buffer sharedLocation; // reference naar shared object public Consumer( Buffer shared ) { super( "Consumer" ); sharedLocation = shared; } JAVA 16 6. PRODUCER/CONSUMER RELATIE ZONDER SYNCHRONISATIE. public void run() // lees sharedLocation's waarde 4 keer en sommeer de waarden { int sum = 0; for ( int count = 1; count <= 4; count++ ) { try // sleep 0 to 3 seconden en lees waarde uit de buffer en tel bij som { Thread.sleep( ( int ) ( Math.random() * 3001 ) ); sum += sharedLocation.get(); } // indien sleeping thread interrupted druk stack trace catch ( InterruptedException exception ) { exception.printStackTrace(); } } System.err.println( getName() + " read values totaling: " + sum + ".\nTerminating " + getName() + "."); } } // einde class Consumer JAVA 17 6. PRODUCER/CONSUMER RELATIE ZONDER SYNCHRONISATIE. public class UnsynchronizedBuffer implements Buffer { private int buffer = -1; // gedeeld door producer en consumer threads // plaats waarde in buffer public void set( int value ) { System.err.println( Thread.currentThread().getName() + " writes " + value ); buffer = value; } // return waarde uit buffer public int get() { System.err.println( Thread.currentThread().getName() + " reads " + buffer ); return buffer; } } JAVA 18 6. PRODUCER/CONSUMER RELATIE ZONDER SYNCHRONISATIE. public class SharedBufferTest { public static void main( String [] args ) { // instantiatie van het shared object gebruikt door de threads Buffer sharedLocation = new UnsynchronizedBuffer(); // instantiatie van producer and consumer objecten Producer producer = new Producer( sharedLocation ); Consumer consumer = new Consumer( sharedLocation ); producer.start(); // start producer thread consumer.start(); // start consumer thread } } JAVA 19 6. PRODUCER/CONSUMER RELATIE ZONDER SYNCHRONISATIE. Consumer reads -1 Producer writes 1 Consumer reads 1 Consumer reads 1 Consumer reads 1 Consumer read values totaling: 2. Terminating Consumer. Producer writes 2 Producer writes 3 Producer writes 4 Producer done producing. Terminating Producer. Producer writes 1 Producer writes 2 Consumer reads 2 Producer writes 3 Consumer reads 3 Producer writes 4 Producer done producing. Terminating Producer. Consumer reads 4 Consumer reads 4 Consumer read values totaling: 13. Terminating Consumer. JAVA 20 6. PRODUCER/CONSUMER RELATIE ZONDER SYNCHRONISATIE. Producer writes 1 Consumer reads 1 Producer writes 2 Consumer reads 2 Producer writes 3 Consumer reads 3 Producer writes 4 Producer done producing. Terminating Producer. Consumer reads 4 Consumer read values totaling: 10. Terminating Consumer. JAVA 21 7. PRODUCER/CONSUMER RELATIE MET SYNCHRONISATIE. Uit vorige voorbeeld blijkt synchronisatie noodzakelijk. Threads die toegang nemen tot een shared object merken zelf niets van de synchronisatie. Synchronisatiecode komt in de set en get methoden van SynchronizedBuffer. Er wordt een extra attribuut gebruikt: int occupiedBufferCount. Dit is een ‘condition’ variabele. Hiermee wordt de communicatie geregeld, mag de buffer gevuld/geledigd worden? Het activeren van de wait methode brengt de thread in Wait toestand waardoor de blokkering voor het gesynchroniseert object beëindigd wordt (release lock). public class SynchronizedBuffer implements Buffer { private int buffer = -1; // // gedeeld door producer en consumer threads private int occupiedBufferCount = 0; // geeft aantal bezette buffers JAVA 22 7. PRODUCER/CONSUMER RELATIE MET SYNCHRONISATIE. public synchronized void set( int value ) // plaats waarde in buffer { String name = Thread.currentThread().getName(); // voor afdrukken status thread while ( occupiedBufferCount == 1 ) // zolang buffer niet leeg, plaats thread in Wait toestand { try // afdrukken thread en buffer status { System.err.println( name + " tries to write." ); displayState( "Buffer full. " + name + " waits." ); wait(); } catch ( InterruptedException exception ) { exception.printStackTrace(); } } buffer = value; // plaats nieuwe waarde in buffer ++occupiedBufferCount; // registreer dat buffer gevuld is… consumer moet buffer ledigen displayState( name + " writes " + buffer ); notify(); // verwittig de thread in Wait toestand om naar Ready toestand over te gaan JAVA } // einde methode set; beëindig blokkering op SynchronizedBuffer (release lock) 23 7. PRODUCER/CONSUMER RELATIE MET SYNCHRONISATIE. public synchronized int get() // levert waarde uit buffer { String name = Thread.currentThread().getName(); while ( occupiedBufferCount == 0 ) // zolang buffer leeg, plaats thread in Wait toestand { try // afdrukken thread en buffer status { System.err.println( name + " tries to read." ); displayState( "Buffer empty. " + name + " waits." ); wait(); } catch ( InterruptedException exception ) { exception.printStackTrace(); } } --occupiedBufferCount; // registreer dat buffer leeg is… producer moet buffer vulllen displayState( name + " reads " + buffer ); notify(); // verwittig de thread in Wait toestand om naar Ready toestand over te gaan return buffer ; } // einde methode get; beëindig blokkering opJAVA SynchronizedBuffer (release lock) 24 7. PRODUCER/CONSUMER RELATIE MET SYNCHRONISATIE. // toon huidige operatie en buffer status public void displayState( String operation ) { StringBuffer outputLine = new StringBuffer( operation ); outputLine.setLength( 40 ); outputLine.append( buffer + "\t\t" + occupiedBufferCount ); System.err.println( outputLine ); System.err.println(); } } // einde class SynchronizedBuffer • Zonder notify blijft de andere thread in Waiting toestand en veroorzaakt deadlock. • Indien met meer dan 2 threads: gebruik notifyAll. JAVA 25 7. PRODUCER/CONSUMER RELATIE MET SYNCHRONISATIE. public class SharedBufferTest2 { public static void main( String [] args ) { SynchronizedBuffer sharedLocation = new SynchronizedBuffer(); StringBuffer columnHeads = new StringBuffer( "Operation" ); columnHeads.setLength( 40 ); columnHeads.append( "Buffer\t\tOccupied Count" ); System.err.println( columnHeads ); System.err.println(); sharedLocation.displayState( "Initial State" ); // instantiatie van producer and consumer objecten Producer producer = new Producer( sharedLocation ); Consumer consumer = new Consumer( sharedLocation ); producer.start(); // start producer thread consumer.start(); // start consumer thread } } JAVA 26 7. PRODUCER/CONSUMER RELATIE MET SYNCHRONISATIE. Output 1. Operation Buffer Occupied Count Initial State -1 0 Buffer empty. Consumer waits. -1 0 Producer writes 1 1 1 Consumer reads 1 1 0 Consumer tries to read. Buffer empty. Consumer waits. 1 0 Producer writes 2 2 1 Consumer reads 2 2 0 Producer writes 3 3 1 Consumer tries to read. JAVA 27 7. PRODUCER/CONSUMER RELATIE MET SYNCHRONISATIE. Output 1. Vervolg… Consumer reads 3 3 0 Consumer tries to read. Buffer empty. Consumer waits. 3 0 Producer writes 4 4 1 Consumer reads 4 Producer done producing. Terminating Producer. 4 0 Consumer read values totaling: 10. Terminating Consumer. JAVA 28 7. PRODUCER/CONSUMER RELATIE MET SYNCHRONISATIE. Output 2. Operation Buffer Occupied Count Initial State -1 0 Consumer tries to read. Buffer empty. Consumer waits. -1 0 Producer writes 1 1 1 Consumer reads 1 1 0 Producer writes 2 Producer tries to write. Buffer full. Producer waits. 2 1 2 1 Consumer reads 2 2 0 Producer writes 3 3 1 Consumer reads 3 3 0 Producer writes 4 4 1 JAVA 29 7. PRODUCER/CONSUMER RELATIE MET SYNCHRONISATIE. Output 2. Vervolg… Producer done producing. Terminating Producer. Consumer reads 4 4 0 Consumer read values totaling: 10. Terminating Consumer. JAVA 30 7. PRODUCER/CONSUMER RELATIE MET SYNCHRONISATIE. Output 3. Operation Buffer Occupied Count Initial State -1 0 Producer writes 1 1 1 Consumer reads 1 1 0 Producer writes 2 Consumer reads 2 2 2 1 0 Producer writes 3 3 1 Consumer reads 3 3 0 Producer writes 4 4 1 Producer done producing. Terminating Producer. Consumer reads 4 4 0 Consumer read values totaling: 10. Terminating Consumer. JAVA 31 OEFENING SYNCHRONISATIE: RESTAURANT Simuleer de werking in een restaurant: er is één kok die orders klaarmaakt en er zijn twee kelners (Sofie en Hendrik) die de orders naar de klant brengen. Stel een random vertraging in van 0 tot 2 seconden bij de kok en de kelners. Na 10 orders sluit het restaurant (voedsel is op). JAVA 32 OEFENING SYNCHRONISATIE: RESTAURANT Gebruik onderstaande klasse voor de orders: class Order { private static int i = 1; private int count = i++; public Order() { if (count > 10) { System.out.println("Voedsel is op, sluiten!"); System.exit(0); } } public String toString() { return "Order " + count; } } JAVA 33 class Kelner extends Thread OPLOSSING SYNCHRONISATIE: RESTAURANT { private Restaurant restaurant; private String naam; public Kelner(Restaurant r, String n) { restaurant = r; naam = n; start(); } public void run() { while(true) { try { sleep((int) (Math.random()*2001)); } catch(InterruptedException e) { throw new RuntimeException(e); } Order order = restaurant.getOrder(); System.out.println("Kelner " + naam + " krijgt " + order); } } } JAVA 34 class Kok extends Thread { private Restaurant restaurant; OPLOSSING SYNCHRONISATIE: RESTAURANT public Kok(Restaurant r) { restaurant = r; start(); } public void run() { while(true) { try { sleep((int) (Math.random()*2001)); restaurant.setOrder(new Order()); } catch(InterruptedException e) { throw new RuntimeException(e); } } } } JAVA 35 OPLOSSING SYNCHRONISATIE: RESTAURANT public class Restaurant { private Order order; public synchronized void setOrder(Order o) { while (order!=null) try { wait(); } catch(InterruptedException e) { throw new RuntimeException(e); } order=o; notifyAll(); } JAVA 36 OPLOSSING SYNCHRONISATIE: RESTAURANT public synchronized Order getOrder() { while (order==null) { try { wait(); } catch(InterruptedException e) { throw new RuntimeException(e); } } Order ref=order; order = null; notifyAll(); return ref; } JAVA 37 OPLOSSING SYNCHRONISATIE: RESTAURANT public static void main(String[] args) { Restaurant restaurant = new Restaurant(); new Kelner(restaurant, "Sofie"); new Kelner(restaurant, "Hendrik"); new Kok(restaurant); } } JAVA 38 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Synchronisatie zoals in vorig punt kan tot inefficiëntie leiden, er kan veel tijd verloren gaan in de Wait toestand. Als de producer sneller is, moet die steeds wachten op de consumer om de volgende waarde te kunnen leveren. Als de consumer sneller is, moet die steeds wachten op de producer die de waarde levert. Zelfs als ze ongeveer even snel zijn zullen ze op elkaar moeten wachten omdat ze ‘tegenover elkaar verlopen’. We voorzien extra buffer ruimte : een circulaire buffer. De circulaire buffer is enkel wanneer beide threads ongeveer even snel zijn. De juiste grootte van de buffer is cruciaal om de thread-wait tijd te minimaliseren. Het voorbeeld gebruikt een circulaire buffer van 3 elementen en werkt met Swing componenten. Swing componenten zijn niet thread-safe. Daarom gebruiken we een event-dispatching thread. JAVA 39 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Alle interactie met swingGUIcomponenten mag niet door meerdere threads ‘tegelijkertijd’ gebeuren. De swing klasse SwingUtilities voorziet de statische methode invokeLater. Het argument moet een object zijn dat de interface Runnable (package java.lang) implementeert. Runnable heeft als enige methode void run(). Ieder thread is een Runnable object. invokeLater zal GUI verwerkende opdrachten laten uitvoeren als deel van de event-dispatching thread. De uitvoering gebeurt nadat alle in verzoek zijnde (‘pending’) AWT events zijn verwerkt. invokeLater is asynchroon, wacht niet tot de opdracht is uitgevoerd. invokeAndWait is synchroon, opgelet voor deadlock. JAVA 40 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class RunnableOutput. import javax.swing.*; public class RunnableOutput implements Runnable { private JTextArea outputArea; private String messageToAppend; public RunnableOutput( JTextArea output, String message ) { outputArea = output; // een referentie naar de JTextArea waar de output naar toe moet messageToAppend = message; // de boodschap die in JTextArea moet toegevoegd worden } public void run() // wordt door SwingUtilities.invokeLater gebruikt voor activatie { outputArea.append( messageToAppend ); } } JAVA 41 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class Producer. // Producer’s run methode is een thread die de waarden 11 tot 20 opslaat in sharedLocation. import javax.swing.*; public class Producer extends Thread { private Buffer sharedLocation; // reference naar shared object private JTextArea outputArea; public Producer( Buffer shared, JTextArea output ) { super( "Producer" ); sharedLocation = shared; outputArea = output; } JAVA 42 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class Producer. public void run() { for ( int count = 11; count <= 20; count ++ ) { try // sleep 0 tot 3 seconde voordat de waarde in Buffer wordt geplaatst { Thread.sleep( ( int ) ( Math.random() * 3000 ) ); sharedLocation.set( count ); } catch ( InterruptedException exception ) { exception.printStackTrace(); } } String name = getName(); SwingUtilities.invokeLater( new RunnableOutput( outputArea, "\n" + name + " done producing.\n" + name + " terminated.\n" ) ); } // einde methode run } // einde class Producer JAVA 43 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class Consumer. // Consumer’s run methode is een thread die 10 maal een waarde ophaalt uit sharedLocation. import javax.swing.*; public class Consumer extends Thread { private Buffer sharedLocation; // reference naar shared object private JTextArea outputArea; public Consumer( Buffer shared, JTextArea output ) { super( "Consumer" ); sharedLocation = shared; outputArea = output; } JAVA 44 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class Consumer. public void run() { int sum = 0; for ( int count = 1; count <= 10; count++ ) { try // sleep 0 tot 3 seconde voordat de waarde uit Buffer wordt gelezen en gesommeerd { Thread.sleep( ( int ) ( Math.random() * 3001 ) ); sum += sharedLocation.get(); } catch ( InterruptedException exception ) { exception.printStackTrace(); } } String name = getName(); SwingUtilities.invokeLater( new RunnableOutput( outputArea, "\nTotal " + name + " consumed: " + sum + ".\n" + name + " terminated.\n ") ); } } // einde class Consumer JAVA 45 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class CircularBuffer. // CircularBuffer synchroniseert toegang tot een array van shared buffers. import javax.swing.*; public class CircularBuffer implements Buffer { private int buffers[] = { -1, -1, -1 }; // elk array element is een buffer private int occupiedBufferCount = 0; private int readLocation = 0, writeLocation = 0; // onderhouden de lees/schrijf positie in de array private JTextArea outputArea; // reference naar GUI component voor output public CircularBuffer( JTextArea output ) { outputArea = output; } JAVA 46 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class CircularBuffer. public synchronized void set( int value ) // plaats waarde in buffer { String name = Thread.currentThread().getName(); while ( occupiedBufferCount == buffers.length ) // zolang er geen plaats { try // afdrukken thread en buffer status, plaats thread in Wait toestand { SwingUtilities.invokeLater( new RunnableOutput( outputArea, "\nAll buffers full. " + name + " waits." ) ); wait(); } catch ( InterruptedException exception ) { exception.printStackTrace(); } } JAVA 47 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class CircularBuffer. buffers[ writeLocation ] = value; // plaats waarde op writeLocation in buffers SwingUtilities.invokeLater( new RunnableOutput( outputArea, // update Swing GUI component "\n" + name + " writes " + buffers[ writeLocation ] + " ") ); ++occupiedBufferCount; // en verhoog het aantal bezette buffers writeLocation = ( writeLocation + 1 ) % buffers.length; // update voor toekomstige schrijfoperatie SwingUtilities.invokeLater( new RunnableOutput( // toon de inhoud van de shared buffers outputArea, createStateOutput() ) ); notify(); // breng waiting thread (als er een is) terug naar ready toestand } // einde methode set JAVA 48 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class CircularBuffer. public synchronized int get() // levert waarde uit buffer { String name = Thread.currentThread().getName(); // while no data to read, place thread in waiting state { try // afdrukken thread en buffer status, plaats thread in Wait toestand { SwingUtilities.invokeLater( new RunnableOutput( outputArea, "\nAll buffers empty. " + name + " waits.") ); wait(); } catch ( InterruptedException exception ) { exception.printStackTrace(); } } JAVA 49 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class CircularBuffer. int readValue = buffers[ readLocation ]; // haal waarde op readLocation uit buffers SwingUtilities.invokeLater( new RunnableOutput( outputArea, // update Swing GUI component "\n" + name + " reads " + readValue + " ") ); --occupiedBufferCount; // er is een buffer vrijgekomen readLocation = ( readLocation + 1 ) % buffers.length; // update voor toekomstige leesoperatie SwingUtilities.invokeLater( new RunnableOutput( // toon de inhoud van de shared buffers outputArea, createStateOutput() ) ); notify(); // breng waiting thread (als er een is) terug naar ready toestand return readValue; } // einde methode get JAVA 50 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class CircularBuffer. public String createStateOutput() { // eerste lijn status informatie String output = "(buffers occupied: " + occupiedBufferCount + ")\nbuffers: "; for ( int i = 0; i < buffers.length; i++ ) output += " " + buffers[ i ] + " "; // tweede lijn status informatie output += "\n "; for ( int i = 0; i < buffers.length; i++ ) output += "---- "; // derde lijn status informatie output += "\n “; JAVA 51 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class CircularBuffer. // plaats indicators R(readLocation) en W (writeLocation) // onder de betreffende buffer locaties for ( int i = 0; i < buffers.length; i++ ) if ( i == writeLocation && writeLocation == readLocation ) output += " WR "; else if ( i == writeLocation ) output += " W "; else if ( i == readLocation ) output += " R "; else output += " "; output += "\n"; return output; } // einde methode createStateOutput } // einde class CircularBuffer JAVA 52 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class CircularBufferTest. // CircularBufferTest toont twee threads die gebruik maken van een circulaire buffer. import java.awt.*; import java.awt.event.*; import javax.swing.*; // creatie en activatie van de producer en consumer threads public class CircularBufferTest extends JFrame { JTextArea outputArea; // opbouw GUI public CircularBufferTest() { super( "Demonstrating Thread Synchronizaton" ); outputArea = new JTextArea( 20,30 ); outputArea.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) ); getContentPane().add( new JScrollPane( outputArea ) ); setSize( 310, 500 ); setVisible( true ); JAVA 53 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Class CircularBufferTest. // creatie van het shared object CircularBuffer dat gedeeld zal worden door beide threads CircularBuffer sharedLocation = new CircularBuffer( outputArea ); // afdrukken van de initiële status van de buffers in CircularBuffer SwingUtilities.invokeLater( new RunnableOutput( outputArea, sharedLocation.createStateOutput() ) ); // creatie van de producer en consumer threads Producer producer = new Producer( sharedLocation, outputArea ); Consumer consumer = new Consumer( sharedLocation, outputArea ); producer.start(); // start producer thread consumer.start(); // start consumer thread } // einde constructor public static void main ( String args[] ) { CircularBufferTest application = new CircularBufferTest(); application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); } } // einde class CirclularBufferTest JAVA 54 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. JAVA Waarde in laatste buffer geplaatst. De volgende waarde komt in de eerste buffer. 55 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Circulair buffer effect—de vierde waarde wordt in de eerste buffer geplaatst. Waarde in laatste buffer geplaatst. De volgende waarde komt in de eerste buffer. JAVA Circulair buffer effect—de zevende waarde wordt in de eerste buffer geplaatst. 56 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. Waarde in laatste buffer geplaatst. De volgende waarde komt in de eerste buffer. JAVA Circulair buffer effect—de tiende waarde wordt in de eerste buffer 57 geplaatst. 8. PRODUCER/CONSUMER RELATIE: CIRCULAIRE BUFFER. JAVA 58 9. DEAMON THREADS. Een thread die als ondersteuning van andere threads meedraait. De deamon thread verhindert niet dat een programma beëindigd wordt. Java’s garbage collector is een voorbeeld van een deamon thread. We markeren een thread als deamon met de methode setDeamon(true). Dit moet wel gebeuren voordat de start methode van de thread geactiveerd wordt. JAVA 59 10. RUNNABLE INTERFACE. Een klasse die al subklasse is kan niet meer erven van Thread; er is geen meervoudige overerving in Java toegelaten. Dergelijke klasse kan wel de interface Runnable implementeren (zoals overigens ook klasse Thread doet). De klasse thread voorziet vijf constructors met een Runnable object als argument. public Thread(Runnable runnableObject) public Thread(Runnable runnableObject, String threadName) VOORBEELD: een applet met twee innerklassen die de interface Runnable implementeren. Eén voor beheer van threads aangemaakt in de applet. Eén voor GUI updates. Codeertechnieken voor suspend, resume en stop worden geïllustreerd (afgekeurde methoden van Thread). Door thread synchronisatie en Objects methode wait en notify. JAVA 60 10. RUNNABLE INTERFACE. JAVA 61 10. RUNNABLE INTERFACE. // Class RandomCharacters demonstreert the Runnable interface import java.awt.*; import java.awt.event.*; import javax.swing.*; public class RandomCharacters extends JApplet implements ActionListener { private String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private final static int SIZE = 3; private JLabel outputs[]; private JCheckBox checkboxes[]; private Thread threads[]; private boolean suspended[]; JAVA 62 10. RUNNABLE INTERFACE. public void init() //aanmaak GUI en arrays { outputs = new JLabel[ SIZE ]; threads = new Thread[ SIZE ]; checkboxes = new JCheckBox[ SIZE ]; suspended = new boolean[ SIZE ]; Container container = getContentPane(); container.setLayout( new GridLayout( SIZE, 2, 5, 5 ) ); for ( int count = 0; count < SIZE; count++ ) { outputs[ count ] = new JLabel(); outputs[ count ].setBackground( Color.GREEN ); outputs[ count ].setOpaque( true ); container.add( outputs[ count ] ); checkboxes[ count ] = new JCheckBox( "Suspended" ); checkboxes[ count ].addActionListener( this ); container.add( checkboxes[ count ] ); } } // end method init JAVA 63 10. RUNNABLE INTERFACE. public void start() // creatie en start threads telkens start methode van de Applet wordt geactiveerd { for ( int count = 0; count < threads.length; count++ ) { // create Thread; initialize object that implements Runnable threads[ count ] = new Thread( new RunnableObject(), "Thread " + ( count + 1 ) ); threads[ count ].start(); // begin executing Thread } } private int getIndex( Thread current ) // bepaal thread locatie in threads array { for ( int count = 0; count < threads.length; count++ ) if ( current == threads[ count ] ) return count; return -1; } JAVA 64 10. RUNNABLE INTERFACE. public synchronized void stop() // stop alle threads als stop methode van de Applet wordt geactiveerd { for ( int count = 0; count < threads.length; count++ ) threads[ count ] = null; // zet references op null zodat de threads run methode te beëindigt notifyAll(); // notify all waiting threads, so they can terminate } public synchronized void actionPerformed( ActionEvent event ) //verwerk button events { for ( int count = 0; count < checkboxes.length; count++ ) { if ( event.getSource() == checkboxes[ count ] ) { suspended[ count ] = !suspended[ count ]; //verander labelkleur bij suspend/resume outputs[ count ].setBackground( suspended[ count ] ? Color.RED : Color.GREEN ); if ( !suspended[ count ] ) //bij resume: verzeker dat de thread tot executie overgaat notifyAll(); return; } } } JAVA 65 10. RUNNABLE INTERFACE. private class RunnableObject implements Runnable // inner klasse om de threads te besturen { public void run() // plaats willekeurig letters in GUI label { final Thread currentThread = Thread.currentThread(); //moeten final omdat inner class final int index = getIndex( currentThread ); // ernaar refereert while ( threads[ index ] == currentThread ) //deze voorwaarde kan de thread doen beëindigen { try //dit gebeurt door thread[index] op null te zetten { Thread.sleep( ( int ) ( Math.random() * 1000 ) ); // sleep 0 tot 1 seconde // determine whether thread should suspend execution; synchronized( RandomCharacters.this ) //moet execution suspend worden { while ( suspended[ index ] && threads[ index ] == currentThread ) { RandomCharacters.this.wait(); // tijdelijk suspend thread executie } } // einde synchronized block } catch ( InterruptedException exception ) { exception.printStackTrace(); } JAVA 66 10. RUNNABLE INTERFACE. SwingUtilities.invokeLater( new Runnable() { public void run() //kies willekeurig een character en toon in JLabel { char displayChar = alphabet.charAt( ( int ) ( Math.random() * 26 ) ); outputs[ index ].setText( currentThread.getName() + ": " + displayChar ); } } ); // einde anonieme inner class } // einde while methode run System.err.println( currentThread.getName() + " terminating" ); } // einde method run } // einde private inner class RunnableObject } // einde class RandomCharacters JAVA 67 Oefening: Responsible GUI. Schrijf een GUI applicatie die de interactiviteit met de gebruiker blijft verzorgen. Na het activeren van een taak zal niet gewacht worden tot die eventeel tijdslopende taak voltooid is. De applicatie kan onmiddellijk nieuwe verzoeken van de gebruiker accepteren. Voorzie een JFrame met drie Jbuttons (taak1, taak2, taak3) die een taak activeren. Je kiest voor willekeurige slaaptijd van 0 tot 10 seconden om ‘het werk’ te simuleren.Een taak informeert steeds in het JTextArea dat ze gestart is “Taak1 gestart”. Wanneer de taak gaat eindigen zal ze de regel verder aanpassen door de uitbreiding “... volbracht”. JAVA 68 Oefening: Responsible GUI. JAVA 69 OPLOSSING RESPONSIBLE GUI. import java.awt.*; import java.awt.event.*; import javax.swing.*; class ResponsiveGUI extends JFrame { private JButton btnTask1, btnTask2, btnTask3; private JTextArea edOut; private ActionListener buttonHandler; public ResponsiveGUI() { super("Responsive GUI"); edOut = new JTextArea(10, 20); edOut.setEditable(false); buttonHandler = new ActionListener() { public void actionPerformed(ActionEvent e) { if (e.getSource()==btnTask1) { new Task1(edOut).start(); } // enz … tot btnTaskn } }; JAVA 70 OPLOSSING RESPONSIBLE GUI. btnTask1 = new JButton("Taak1"); // enz tot btnTaskn btnTask1.addActionListener(buttonHandler); // enz tot btnTaskn Container c = getContentPane(); c.setLayout(new FlowLayout()); c.add(btnTask1); // enz tot btnTaskn c.add(new JScrollPane(edOut)); } JAVA 71 OPLOSSING RESPONSIBLE GUI. public static void main(String arg[]) { ResponsiveGUI rpGUI = new ResponsiveGUI(); rpGUI.setSize(300,100); rpGUI.setVisible(true); rpGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } } class UpdateMessage implements Runnable { String mes; JTextArea edOut; public UpdateMessage(String m, JTextArea t) { mes=m; edOut = t; } public void run() { StringBuffer buf = new StringBuffer(edOut.getText()); int i = buf.indexOf(mes); edOut.setText(buf.replace(i+mes.length()-1, i+mes.length(),"... volbracht\n").toString()); } } JAVA 72 OPLOSSING RESPONSIBLE GUI. class Task1 extends Thread { JTextArea edOut; public Task1(JTextArea f) { edOut = f; } public void run() { String mes = "Taak1 gestart\n"; edOut.append(mes); try { sleep((int)(Math.random()*10001)); } catch(InterruptedException e) { throw new RuntimeException(e); } SwingUtilities.invokeLater(new UpdateMessage(mes, edOut)); } } // enz … tot class Taskn JAVA 73