public class Sesselfabrik extends Thread { // Nur für farbige Konsolenausgabe (unwichtig): public static final String ANSI_RESET = "\u001B[0m"; public static final String ANSI_GREEN = "\u001B[32m"; public static final String ANSI_BLUE = "\u001B[34m"; public static final String ANSI_RED = "\u001B[31m"; private int sesselBestellbar = 0; private int lagerPlaetzeFrei = 20; private boolean rampeBelegt = false; // synchronize variable sesselBestellbar private final Object lockSessel = new Object(); // synchronize variable lagerPlaetzeFrei private final Object lockLager = new Object(); // synchronize variable rampeBelegt private final Object lockRampe = new Object(); @Override public void run() { try { while (true) { sesselEinlagern(); } } catch (Exception e) { if (!(e instanceof InterruptedException)) { System.out.println("Unexpected Exception in Sesselfabrik.run()"); } } } public void sesselEinlagern() throws InterruptedException { System.out.println(ANSI_BLUE + "lockLager: try to get lock (in sesselEinlagern)" + ANSI_RESET); synchronized (lockLager) { // -> Zugriff auf lagerPlaetzeFrei nur für // einen einzigen Thread zur selben Zeit. System.out.println(ANSI_BLUE + "lockLager: locked" + ANSI_RESET); /* Wenn kein Platz frei ist, wollen wir warten wir bis wieder Platz frei ist. Dazu rufen wir in diesem Fall die wait()-Methode auf (die jedes Objekt besitzt, da sie in Object definiert ist): "wait() causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object". Warum brauchen wir das wait() überhaupt? Weglassen und dauerhaft überprüfen in der Form "while(lagerPlaetzeFrei <= 0){}" ginge auch, allerdings würde dann das sog. Busy-Waiting vorliegen, was wir vermeiden wollen. Wichtig: Durch den Aufruf lock.wait() wird das intrinsische Lock lock wieder freigegeben, d. h. jetzt kann der synchronized(lock)-Block von anderen betreten werden! Wird nun lock.notify() aufgerufen, so kann nicht einfach dort weitergemacht werden, wo vorher mit wait gewartet wurde, schließlich könnte das Lock mittlerweile belegt sein, d. h. hier wird erneut gewartet, bis das Lock frei ist. Der Thread befindet sich nach einem lock.wait() im Zustand blocked (vgl. Schaubild auf den Folien). An einer anderen Stelle (nämlich wenn wir wissen, dass wieder Platz frei ist) müssen wir also lockLager.notify() aufrufen, damit hier fortgefahren wird. wait() und notify() gehen also Hand in Hand (vgl. sesselAbholen()). Warum while()? Durch wait() haben wir Busy-Waiting vermieden und könnten statt while einfach ein if nehmen, allerdings sollte ein wait, welches mit einer Bedingung einhergeht wie in diesem Beispiel, immer in einer Schleife erneut auf diese Bedingung geprüft werden. Warum? Es könnte ein böser Mann kommen und einfach lockLager.notify() aufrufen, obwohl das Lager immernoch leer ist. Gegen solche bösen Menschen sichern wir uns ab. */ while (lagerPlaetzeFrei <= 0) { System.out.println("Kann nicht produzieren, warte auf Platz."); lockLager.wait(); } lagerPlaetzeFrei--; // ein Platz belegt durch neuen Sessel. System.out.println("Sessel produziert und ins Lager gestellt."); } /* Jetzt müssen wir auch noch die Anzahl verfügbar Sessel erhöhen. Dazu müssen wir wieder sicherstellen, dass nur ein Thread zur selben Zeit auf die Variable sesselBestellbar zugreift. Dazu können wir wieder synchronized(lockSessel) verwenden. Außerdem müssen wir evtl. aktuell wartende Lieferanten darüber informieren, dass wieder Sessel verfügbar sind (vgl. Methode sesselBestellen). */ System.out.println(ANSI_RED + "lockSessel: try to get lock (in sesselEinlagern)" + ANSI_RESET); synchronized (lockSessel) { System.out.println(ANSI_RED + "lockSessel: locked" + ANSI_RESET); sesselBestellbar++; // ein weiterer Sessel ist jetzt verfügbar. System.out.println("Sessel ist jetzt bestellbar."); lockSessel.notify(); System.out.println(ANSI_RED + "lockSessel: unlocked" + ANSI_RESET); } } public void sesselBestellen() { System.out.println(ANSI_RED + "lockSessel: try to get lock (in sesselBestellen)" + ANSI_RESET); synchronized (lockSessel) { System.out.println(ANSI_RED + "lockSessel: locked" + ANSI_RESET); while (sesselBestellbar <= 0) { System.out.println("Kein Sessel verfügbar, warte auf Produktion."); try { lockSessel.wait(); // warte auf neue Produktion System.out.println("Es scheinen wieder Sessel verfügbar zu sein."); } catch (InterruptedException ex) { // einfach weitermache, falls unterbrochen: // InterruptedException wird laut Dokumentation geworfen, // "if any thread interrupted the current thread before or // while the current thread was waiting for a notification. // The interrupted status of the current thread is cleared // when this exception is thrown." System.out.println(ANSI_RED + "interrupted" + ANSI_RESET); } } sesselBestellbar--; System.out.println("Sessel bestellt."); } System.out.println(ANSI_RED + "lockSessel: unlocked" + ANSI_RESET); } public void sesselAbholen() { System.out.println(ANSI_BLUE + "lockLager: try to get lock (in sesselAbholen)" + ANSI_RESET); synchronized (lockLager) { System.out.println(ANSI_BLUE + "lockLager: locked" + ANSI_RESET); lagerPlaetzeFrei++; System.out.println("Sessel abgeholt."); lockLager.notify(); // wieder Plätze frei (vgl. sesselEinlagern()) } System.out.println(ANSI_BLUE + "lockLager: unlocked" + ANSI_RESET); } public void rampeBelegen() { System.out.println(ANSI_GREEN + "lockRampe: try to get lock (in rampeBelegen)" + ANSI_RESET); synchronized (lockRampe) { System.out.println(ANSI_GREEN + "lockRampe: locked" + ANSI_RESET); // Gleiches Konstrukt wie oberhalb, daher nicht näher erläutert. // Sollte die Rampe belegt sein, warten wir (indirekt, d. h. bis wir // benachrichtigt werden), bis sie frei ist und holen dann einen // Sessel ab. while (rampeBelegt == true) { try { lockRampe.wait(); } catch (InterruptedException ex) { } } rampeBelegt = true; System.out.println("Rampe wurde belegt."); } System.out.println(ANSI_GREEN + "lockRampe: unlocked" + ANSI_RESET); } public void rampeFreigeben() { System.out.println(ANSI_GREEN + "lockRampe: try to get lock (in rampeFreigeben)" + ANSI_RESET); synchronized (lockRampe) { System.out.println(ANSI_GREEN + "lockRampe: locked" + ANSI_RESET); rampeBelegt = false; System.out.println("Rampe freigegeben."); lockRampe.notify(); // wartende Lieferanten (in rampeBelegen) benachrichtigen } System.out.println(ANSI_GREEN + "lockRampe: unlocked" + ANSI_RESET); } public static void main(String[] args) { Sesselfabrik fabrik = new Sesselfabrik(); Sessellieferant lieferantA = new Sessellieferant(fabrik, 10); Sessellieferant lieferantB = new Sessellieferant(fabrik, 5); lieferantA.start(); try { Thread.sleep(300); } catch (InterruptedException ex) {} lieferantB.start(); try { Thread.sleep(300); } catch (InterruptedException ex) {} fabrik.start(); try { lieferantA.join(); lieferantB.join(); } catch (InterruptedException ex) {}; // wenn beide fertig sind, dann unterbreche die Produktion fabrik.interrupt(); System.out.println("Fertig."); } }