Muster nebenläufiger Programmierung concurrent Packet von Java Alois Schü;e AOSD 1
Concurrent Packet In diesem Teil der Veranstaltung werde Muster nebenläufiger Programmierung diskueert. Dazu wird das concurrent Packet von Java betrachtet. Alois Schü;e AOSD 2
Concurrent Packet - Überblick Standard- Mi;el von Java bezogen auf Nebenläufigkeit sind: Threads mit dem Interface Runnable SynchronisaEons- Mechanismen synchronized, wait, notify und notifyall. Ab Java 5 sind im Paket java.util.concurrent Klassen für Standard- Muster der parallelen Programmierung enthalten, z.b.: Locks Queues Thread- Pooling Scheduling Semaphore Exchanger CountDownLatch CyclicBarrier Alois Schü;e AOSD 3
Concurrent Packet - Locks Lockkonzept Ein Lock ist ein Mi;el, um in mul$threading Umgebungen den gemeinsamen Zugriff auf Ressourcen zu koordinieren. Um eine Ressource nutzen zu können, muss ein Thread den zugehörigen Schlüssel anfordern. Solange ein Thread den Schlüssel besitzt, kann kein anderer Thread die Ressource verwenden, er muss warten. Der den Schlüssel besitzende Thread gibt ihn frei, darau\in kann ein wartender Thread den Schlüssel bekommen und die Ressource verwenden. Dieses Lockkonzept könnte mit synchronized umgesetzt werden. Dabei hat man aber immer die Blockstruktur als Einschränkung. java.util.concurrent.locks beinhaltet Interfaces und Klassen für Locks. Alois Schü;e AOSD 4
Concurrent Packet - Locks Die Klasse TimeUnit wird im Zusammenhang mit Locks verwendet, um eine Zeitdauer in SECONDS, MICROSECONDS, MILLISECONDS oder NANOSECONDS angeben zu können. $ cat TimeUnit/MainClass.java import static java.util.concurrent.timeunit.*; public class MainClass extends Thread { // This field is volatile because two different threads may access it volatile boolean keeprunning = true; public void run() { while (keeprunning) { long now = System.currentTimeMillis(); System.out.printf("%tr%n", now); try { Thread.sleep(1000); // millisecs catch (InterruptedException e) { return; // run In der run- Methode wird die Methode sleep von Thread verwendet. Es wird eine Sekunde geschlafen. Alois Schü;e AOSD 5
Concurrent Packet - Locks $ public void pleasestop() { keeprunning = false; public static void main(string[] args) { MainClass thread = new MainClass(); thread.start(); try { SECONDS.sleep(10); // = MILLISECONDS.sleep(10000) catch (InterruptedException ignore) { thread.pleasestop(); // main Innerhalb main wird SECONDS von TimeUnit verwendet, um das Programm 10 Sekunden lang laufen zu lassen. $ java MainClass 10:19:51 AM 10:19:52 AM 10:19:53 AM 10:19:54 AM 10:19:55 AM 10:19:56 AM 10:19:57 AM 10:19:58 AM 10:19:59 AM 10:20:00 AM $ Alois Schü;e AOSD 6
Concurrent Packet - Locks Das Interface Lock spezifiziert das Verhalten von Lock- Objekten. public interface Lock { void lock(); void lockinterruptible() throws InterruptedException; boolean trylock(); boolean trylock(long time, TimeUnit unit) throws InterruptedException void unlock(); Condition newcondition(); // Erklärung später lock wartet, bis der Objektschlüssel verfügbar ist und belegt ihn dann. unlock gibt das Objekt frei. lockinterruptible funkeoniert wie lock, aber es wird eine Ausnahme geworfen, wenn ein anderer Thread den Thread durch interrupt unterbricht. trylock liefert false, wenn das Objekt nicht verfügbar ist; ansonsten wird das Objekt in Besitz genommen und true returniert. trylock(long, TimeUnit) funkeoniert wie trylock, aber es wird eine maximale Zeitspanne gewartet, wenn das Objekt nicht verfügbar ist. Alois Schü;e AOSD 7
Concurrent Packet Locks - ReentrantLock Die Klasse ReentrantLock implemeneert die Schni;stelle Lock. public class ReentrantLock implements Lock, Serializable { public ReentrantLock(boolean fair); public ReentrantLock; // Methods of Lock void lock(); void lockinterruptible() throws InterruptedException; boolean trylock(); boolean trylock(long time, TimeUnit unit) throws InterruptedException void unlock(); Condition newcondition(); // additional Methods public boolean isfair(); public int getholdcount(); public int getqueuelength(); public boolean isheldbycurrentthread(); public boolean islocked(); protected Thread getowner(); protected Collection<Thread> getqueuedthreda(); Alois Schü;e AOSD 8
Concurrent Packet Locks - ReentrantLock Der Konstruktor kann die Angabe eine fair- Paramerters haben. Wenn mehrere Threads auf den Lock warten, garaneert fair==true, dass der am längsten wartende Thread das Lock- Objekt erhält. isfair liefert den fair- Parameter des Konstruktors zurück. Ein Lock enthält eine Zähler, der bei jedem lock inkremeneert, bei unlock dekremeneert wird. Ein Thread kann also öier lock aufrufen. getholdcount liefert den Wert des Zählers. getqueuelength returniert die Anzahl der auf einen Lock wartenden Threads. isheldbycurrentthread ist true, wenn der aufrufende Thread den Lock hält. islocked ist true, wenn irgendein Thread den Lock hält. getowner(), CollecEon<Thread> getqueuedthreads liefern den Besitzer und die wartenden Threads. Alois Schü;e AOSD 9
Concurrent Packet Locks - ReentrantLock Beispiel: Klasse Konto (Account), Geldabholer (Withdrawer als Thread) Zunächst die Realisierung der Klasse Account mit synchronized. $ cat ReentrantLock/synchronized/WithdrawApp.java class Account { private float balance; public Account (float initialbalance) { balance = initialbalance; public synchronized float getbalance() { return balance; // getbalance public synchronized void withdraw( float amount) { if (amount < 0 balance < amount) throw new IllegalArgumentException("withdraw: wrong amount " + amount); try { Thread.sleep(1000); catch (Exception e) {; balance -= amount; // withdraw // Account synchronized ist erforderlich, da ein Konto von mehreren Threads verwendet werden kann und mindestens einer den Zustand per withdraw ändern kann. Alois Schü;e AOSD 10
Concurrent Packet Locks - ReentrantLock Nun die Realisierung der Klasse Account mi;els Locks. Die Blockstruktur von synchronized muss mi;els lock und unlock nachgebildet werden: import java.util.concurrent.locks.*; private final ReentrantLock lock = new ReentrantLock(true); lock.lock(); try {... finally { lock.unlock(); WichEg: Da im try- Block Ausnahmen auireten können ist mi;els finally sicherzustellen, dass stets unlock aufgerufen wird! Nur so werden gelockte Objekte immer freigegeben. Die Verwendung von lock- unlock ist also aufwendiger, dafür aber universell: ein Thread kann lock aufrufen, ein andere unlock Soll anstelle einer Objektsperre eine Klassensperre deklariert werden, wird die Lock- Variable als sta$c definiert. Alois Schü;e AOSD 11
Concurrent Packet Locks - ReentrantLock $ cat ReentrantLock/ReentrantLock/WithdrawApp.java import java.util.concurrent.locks.*; class Account { private float balance; private final ReentrantLock lock = new ReentrantLock(true); public Account (float initialbalance) { balance = initialbalance; // Account public float getbalance() { lock.lock(); try { return balance; finally {lock.unlock(); // getbalance public void withdraw( float amount) { lock.lock(); try { if (amount < 0 balance < amount) throw new IllegalArgumentException("withdraw: wrong amount " + amount); try { Thread.sleep(1000); catch (Exception e) {; balance -= amount; finally {lock.unlock(); // withdraw // Account Alois Schü;e AOSD 12
Concurrent Packet Locks - ReentrantLock Was muss geändert werden, wenn Jenni und Hannah nicht gleichzeieg Geld abholen dürfen? Idee: Der erste Abholer hält den Lock, der zweite muss abgewiesen werden. Lösung: trylock anstelle von lock $ cat ReentrantLock/tryLock/WithdrawApp.java public void withdraw( float amount ) { if (lock.trylock() == false) return; try { if (amount < 0 balance < amount) throw new IllegalArgumentException("withdraw:...); try { Thread.sleep(1000); catch (Exception e) {; balance -= amount; finally { lock.unlock(); // withdraw Alois Schü;e AOSD 13
Concurrent Packet Locks - CondiEon Die Methode newcondi$on des Interface Lock liefert ein CondiEon- Objekt zurück. Genauer, ein Objekt einer Klasse die die Schni;stelle CondiEon implemeneert. public interface Condition { void await() throm InterruptedException; void awaituninterruptibly(); boolean await(long time Timeunit unit) throm InterruptedException; long awaitnanos(long time) throm InterruptedException; boolean awaituntil(date deadline) throm InterruptedException; void signal(); void signalall(); Die Methoden haben Ähnlichkeit zu wait und noefy. Eine CondiEon ist signalisiert oder nicht signalisiert. Sofort nach ihrer Erzeugung ist sie signalisiert. Ein await- Aufruf ( wait) auf einer signalisierten CondiEon kehrt sofort zurück. Vor Rückkehr von await wird die CondiEon in den nicht signalisierten Zustand versetzt. signal ( noefy) versetzt eine CondiEon in den signalisierten Zustand, weckt also einen wartenden Thread signalall ( noefyall) weckt alle auf die CondiEon wartenden Threads. Alois Schü;e AOSD 14
Concurrent Packet Locks - CondiEon Beispiel: BoundedBuffer, zunächst mit synchronized. $ cat Condition/BoundedBuffer/synchronized/BoundedBufferApp.java class BoundedBuffer { private float[] buffer; private int first, last; private int numberinbuffer = 0, size; BoundedBuffer(int length) { size = length; buffer = new float[size]; first = last = 0; public synchronized void dumpbuffer() { System.err.print("Buffer: "); // use err channel to log for (int i=(first+1)%size, j=0; j<numberinbuffer; j++, i=(i+1)%size) System.err.print(buffer[i] + " "); System.err.println(" "); float Puffer fester Grösse dumpbuffer zum Debuggen des Puffers über stderr Alois Schü;e AOSD 15
Concurrent Packet Locks - CondiEon Beispiel: BoundedBuffer, zunächst mit synchronized. $ cat Condition/BoundedBuffer/synchronized/BoundedBufferApp.java class BoundedBuffer { private float[] buffer; private int first, last; private int numberinbuffer = 0, size; BoundedBuffer(int length) { size = length; buffer = new float[size]; first = last = 0; public synchronized void dumpbuffer() { System.err.print("Buffer: "); // use err channel to log for (int i=(first+1)%size, j=0; j<numberinbuffer; j++, i=(i+1)%size) System.err.print(buffer[i] + " "); System.err.println(" "); float Puffer fester Grösse dumpbuffer zum Debuggen des Puffers über stderr Alois Schü;e AOSD 16
Concurrent Packet Locks - CondiEon Beispiel: BoundedBuffer, zunächst mit synchronized. public synchronized void put(float item) throws InterruptedException { while(numberinbuffer == size) wait(); last = (last+1)%size; numberinbuffer++; buffer[last] = item; dumpbuffer(); notifyall(); public synchronized float get() throws InterruptedException { while(numberinbuffer == 0) wait(); first = (first+1)%size; numberinbuffer--; dumpbuffer(); notifyall(); return buffer[first]; // BoundedBuffer Die Methoden put und get sind mi;els synchronized synchronisiert. last ist Einfügestelle. von first wird gelesen. Alois Schü;e AOSD 17
Der Produzent verwendet die put- Methode: Concurrent Packet Locks - CondiEon class Producer extends Thread { private BoundedBuffer buffer; public Producer(BoundedBuffer b) { buffer = b; public void run() { for(int i = 0; i < 100; i++) { try { buffer.put(i); System.out.println("put " + i); catch (InterruptedException ingnored) {; // Producer Alois Schü;e AOSD 18
Concurrent Packet Locks - CondiEon Wie kann man dies nun mi;els CondiEon realisieren und wo sind die Vorteile? $ cat Condition/BoundedBuffer/condition/BoundedBufferApp.java class BoundedBuffer { private float[] buffer; private int first, last; private int numberinbuffer = 0, size; private ReentrantLock lock = new ReentrantLock(); private final Condition notfull = lock.newcondition(); private final Condition notempty = lock.newcondition(); BoundedBuffer(int length) {... public void dumpbuffer() {... lock ist ein ReentrantLock Objekt. Es gibt zwei Condi$on AKribute, notfull und notempty für das Objekt lock. Alois Schü;e AOSD 19
Concurrent Packet Locks - CondiEon put: public void put(float item) throws InterruptedException { lock.lock(); try { while(numberinbuffer == size) notfull.await(); last = (last+1)%size; numberinbuffer++; buffer[last] = item; dumpbuffer(); notempty.signal(); finally { lock.unlock(); Wenn der Buffer voll ist, wird gewartet, bis eine Condi$on notfull signalisiert wird. Nach dem Schreiben in den Buffer wird signaliert notempty. Alois Schü;e AOSD 20
Concurrent Packet Locks - CondiEon get: public float get() throws InterruptedException { lock.lock(); try { while(numberinbuffer == 0) notempty.await(); first = (first+1)%size; numberinbuffer--; dumpbuffer(); notfull.signal(); return buffer[first]; finally { lock.unlock(); Wenn der Buffer leer ist, wird gewartet, bis eine Condi$on notempty signalisiert wird. Nach dem Lesen des Buffer wird signaliert notfull. Insgesamt ist man also mit Locks und CondiEons flexibler, man kann unterschiedliche Bedingungen signalisieren und so gezielt nur bes$mmte Threads wecken (eben die die auf die CondiEon warten). Alois Schü;e AOSD 21