Gutes Beispiel für Viehzucht?

141

Ich verstehe, was Livelock ist, aber ich habe mich gefragt, ob jemand ein gutes codebasiertes Beispiel dafür hat. Und mit codebasiert meine ich nicht "zwei Personen, die versuchen, in einem Korridor aneinander vorbeizukommen". Wenn ich das noch einmal lese, verliere ich mein Mittagessen.

Alex Miller
quelle
96
Wie wäre es mit einer Software-Simulation von zwei Personen, die versuchen, in einem Korridor aneinander vorbeizukommen?
1800 INFORMATION
36
Verfluche dich! Ich habe mein Mittagessen verloren!
Alex Miller
3
Seltsamerweise angemessen: seuss.wikia.com/wiki/The_Zax
NotMe
Verwandte Witze für neugierige Kerle: Codingarchitect.wordpress.com/2006/01/18/…
Jorjon
4
Zwei Personen, die versuchen, in einem Korridor aneinander vorbeizukommen : gist.github.com/deepankarb/d2dd6f21bc49902376e614d3746b8965 : p
iceman

Antworten:

119

Hier ist ein sehr einfaches Java-Beispiel für Livelock, bei dem ein Mann und eine Frau versuchen, Suppe zu essen, aber nur einen Löffel zwischen sich haben. Jeder Ehepartner ist zu höflich und gibt den Löffel weiter, wenn der andere noch nichts gegessen hat.

public class Livelock {
    static class Spoon {
        private Diner owner;
        public Spoon(Diner d) { owner = d; }
        public Diner getOwner() { return owner; }
        public synchronized void setOwner(Diner d) { owner = d; }
        public synchronized void use() { 
            System.out.printf("%s has eaten!", owner.name); 
        }
    }

    static class Diner {
        private String name;
        private boolean isHungry;

        public Diner(String n) { name = n; isHungry = true; }       
        public String getName() { return name; }
        public boolean isHungry() { return isHungry; }

        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) {
                // Don't have the spoon, so wait patiently for spouse.
                if (spoon.owner != this) {
                    try { Thread.sleep(1); } 
                    catch(InterruptedException e) { continue; }
                    continue;
                }                       

                // If spouse is hungry, insist upon passing the spoon.
                if (spouse.isHungry()) {                    
                    System.out.printf(
                        "%s: You eat first my darling %s!%n", 
                        name, spouse.getName());
                    spoon.setOwner(spouse);
                    continue;
                }

                // Spouse wasn't hungry, so finally eat
                spoon.use();
                isHungry = false;               
                System.out.printf(
                    "%s: I am stuffed, my darling %s!%n", 
                    name, spouse.getName());                
                spoon.setOwner(spouse);
            }
        }
    }

    public static void main(String[] args) {
        final Diner husband = new Diner("Bob");
        final Diner wife = new Diner("Alice");

        final Spoon s = new Spoon(husband);

        new Thread(new Runnable() { 
            public void run() { husband.eatWith(s, wife); }   
        }).start();

        new Thread(new Runnable() { 
            public void run() { wife.eatWith(s, husband); } 
        }).start();
    }
}
Jeremy Elbourn
quelle
6
Muss die getOwnerMethode nicht auch synchronisiert werden? Von effektivem Java "hat die Synchronisation keine Auswirkung, es sei denn, sowohl Lesen als auch Schreiben ".
Sanghyun Lee
Sollte er nicht Thread.join()lieber verwenden als Thread.sleep(), weil er darauf warten möchte, dass der Ehepartner sein Essen macht?
Trost
Was sollen wir tun, um das Problem des Viehbestands in diesem speziellen Beispiel zu überwinden?
Thor
1
Die getOwnerMethode muss synchronisiert werden, da selbst wenn die Methode synchronisiert setOwnerist, dies nicht garantiert, dass der Thread, der getOwnerdas Feld verwendet (oder ownerdirekt auf das Feld zugreift ), die vom anderen Thread vorgenommenen Änderungen sieht setOwner. Dieses Video erklärt dies sehr sorgfältig: youtube.com/watch?v=WTVooKLLVT8
Timofey
2
Sie müssen kein synchronized Schlüsselwort für die setOwnerMethode verwenden, da Lesen und Schreiben eine atomare Aktion für die Referenzvariable ist.
Atiqkhaled
75

Abgesehen von flippigen Kommentaren ist ein bekanntes Beispiel der Code, der versucht, Deadlock-Situationen zu erkennen und zu behandeln. Wenn zwei Threads einen Deadlock erkennen und versuchen, sich gegenseitig "beiseite zu treten", bleiben sie ohne Sorgfalt in einer Schleife stecken, die immer "beiseite tritt" und es nie schafft, sich vorwärts zu bewegen.

Mit "beiseite treten" meine ich, dass sie das Schloss aufheben und versuchen würden, den anderen es erwerben zu lassen. Wir könnten uns die Situation mit zwei Threads vorstellen, die dies tun (Pseudocode):

// thread 1
getLocks12(lock1, lock2)
{
  lock1.lock();
  while (lock2.locked())
  {
    // attempt to step aside for the other thread
    lock1.unlock();
    wait();
    lock1.lock();
  }
  lock2.lock();
}

// thread 2
getLocks21(lock1, lock2)
{
  lock2.lock();
  while (lock1.locked())
  {
    // attempt to step aside for the other thread
    lock2.unlock();
    wait();
    lock2.lock();
  }
  lock1.lock();
}

Abgesehen von den Rennbedingungen haben wir hier eine Situation, in der beide Threads, wenn sie gleichzeitig eintreten, in der inneren Schleife laufen, ohne fortzufahren. Dies ist offensichtlich ein vereinfachtes Beispiel. Eine naiive Lösung wäre, eine gewisse Zufälligkeit in die Wartezeit der Threads zu setzen.

Die richtige Lösung besteht darin, immer die Sperrenerbschaft zu respektieren . Wählen Sie eine Reihenfolge, in der Sie die Schlösser erwerben, und halten Sie sich daran. Wenn beispielsweise beide Threads immer lock1 vor lock2 erhalten, besteht keine Möglichkeit eines Deadlocks.

1800 INFORMATIONEN
quelle
Ja, das verstehe ich. Ich suche nach einem tatsächlichen Codebeispiel dafür. Die Frage ist, was "beiseite treten" bedeutet und wie es ein solches Szenario erzeugt.
Alex Miller
Ich verstehe, dass dies ein erfundenes Beispiel ist, aber ist es wahrscheinlich, dass dies zu einem Viehbestand führen könnte? Wäre es nicht viel wahrscheinlicher, dass sich irgendwann ein Fenster öffnet, in dem eine Funktion beide erfassen kann, aufgrund von Inkonsistenzen in der Zeit, in der die Threads laut ausgeführt werden sollen, und wann sie geplant sind.
DubiousPusher
Obwohl es kein stabiles Vieh ist, weil es offensichtlich irgendwann ausbrechen wird, denke ich, dass es gut genug zur Beschreibung passt
1800 INFORMATION
Exzellentes und aussagekräftiges Beispiel.
Alecov
7

Da keine Antwort als akzeptierte Antwort markiert ist, habe ich versucht, ein Beispiel für eine Live-Sperre zu erstellen.

Das ursprüngliche Programm wurde von mir im April 2012 geschrieben, um verschiedene Konzepte des Multithreading zu erlernen. Dieses Mal habe ich es geändert, um Deadlock, Race Condition, Livelock usw. zu erstellen.

Lassen Sie uns zuerst die Problemstellung verstehen.

Cookie Maker Problem

Es gibt einige Zutatenbehälter : ChocoPowederContainer , WheatPowderContainer . CookieMaker entnimmt etwas Pulver aus Zutatenbehältern , um ein Cookie zu backen . Wenn ein Cookie-Hersteller feststellt, dass ein Container leer ist, sucht er nach einem anderen Container, um Zeit zu sparen. Und wartet, bis Filler den gewünschten Behälter füllt. Es gibt einen Füllstoff, der den Behälter in regelmäßigen Abständen überprüft und eine bestimmte Menge füllt, wenn ein Behälter dies benötigt.

Bitte überprüfen Sie den vollständigen Code auf Github ;

Lassen Sie mich die Implementierung kurz erläutern.

  • Ich starte Filler als Daemon-Thread. So werden die Behälter in regelmäßigen Abständen gefüllt. Um einen Behälter zuerst zu füllen, verschließt er den Behälter -> prüft, ob er etwas Pulver benötigt -> füllt ihn -> signalisiert allen Herstellern, die darauf warten -> entsperrt den Behälter.
  • Ich erstelle CookieMaker und stelle ein, dass es bis zu 8 Cookies parallel backen kann. Und ich starte 8 Threads, um Kekse zu backen.
  • Jeder Maker-Thread erstellt 2 aufrufbare Unter-Threads, um Pulver aus Behältern zu entnehmen.
  • Der Unterfaden verriegelt einen Behälter und prüft, ob genügend Pulver vorhanden ist. Wenn nicht, warten Sie einige Zeit. Sobald der Füllstoff den Behälter füllt, nimmt er das Pulver und entriegelt den Behälter.
  • Jetzt werden andere Aktivitäten abgeschlossen, z. B. Mischen und Backen usw.

Werfen wir einen Blick in den Code:

CookieMaker.java

private Integer getMaterial(final Ingredient ingredient) throws Exception{
        :
        container.lock();
        while (!container.getIngredient(quantity)) {
            container.empty.await(1000, TimeUnit.MILLISECONDS);
            //Thread.sleep(500); //For deadlock
        }
        container.unlock();
        :
}

IngredientContainer.java

public boolean getIngredient(int n) throws Exception {
    :
    lock();
    if (quantityHeld >= n) {
        TimeUnit.SECONDS.sleep(2);
        quantityHeld -= n;
        unlock();
        return true;
    }
    unlock();
    return false;
}

Alles läuft gut, bis Filler die Behälter füllt. Wenn ich jedoch vergesse, den Füllstoff zu starten, oder der Füllstoff unerwartet verlassen wird, ändern die Sub-Threads ständig ihren Status, damit andere Hersteller den Behälter überprüfen können.

Ich habe auch einen Daemon- ThreadTracer erstellt, der Thread-Zustände und Deadlocks überwacht. Dies ist die Ausgabe von der Konsole;

2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:RUNNABLE, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
WheatPowder Container has 0 only.
2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:RUNNABLE]
2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]

Sie werden feststellen, dass Sub-Threads und ihre Zustände ändern und warten.

Amit Kumar Gupta
quelle
4

Ein reales Beispiel (wenn auch ohne exakten Code) sind zwei konkurrierende Prozesse, die live gesperrt werden, um einen SQL Server-Deadlock zu korrigieren, wobei jeder Prozess denselben Wiederholungsalgorithmus für Wiederholungsversuche verwendet. Obwohl es das Glück des Timings ist, habe ich gesehen, dass dies auf separaten Computern mit ähnlichen Leistungsmerkmalen als Reaktion auf eine Nachricht geschieht, die einem EMS-Thema hinzugefügt wurde (z. B. das Speichern einer Aktualisierung eines einzelnen Objektdiagramms mehr als einmal) und nicht gesteuert werden kann die Sperrreihenfolge.

Eine gute Lösung wäre in diesem Fall, konkurrierende Verbraucher zu haben (verhindern Sie die doppelte Verarbeitung so weit oben in der Kette wie möglich, indem Sie die Arbeit auf nicht verwandte Objekte aufteilen).

Eine weniger wünschenswerte (ok, Dirty-Hack) Lösung besteht darin, das Timing-Pech (Art der Kraftunterschiede bei der Verarbeitung) im Voraus zu brechen oder es nach dem Deadlock durch Verwendung verschiedener Algorithmen oder eines zufälligen Elements zu brechen. Dies kann immer noch Probleme verursachen, da die Reihenfolge der Sperren für jeden Prozess möglicherweise "klebrig" ist und dies ein bestimmtes Minimum an Zeit in Anspruch nimmt, das bei der erneuten Wartezeit nicht berücksichtigt wird.

Eine weitere Lösung (zumindest für SQL Server) besteht darin, eine andere Isolationsstufe (z. B. Snapshot) auszuprobieren.

Kit
quelle
2

Ich habe das Beispiel von 2 Personen codiert, die in einem Korridor vorbeikommen. Die beiden Fäden meiden sich, sobald sie feststellen, dass ihre Richtungen gleich sind.

public class LiveLock {
    public static void main(String[] args) throws InterruptedException {
        Object left = new Object();
        Object right = new Object();
        Pedestrian one = new Pedestrian(left, right, 0); //one's left is one's left
        Pedestrian two = new Pedestrian(right, left, 1); //one's left is two's right, so have to swap order
        one.setOther(two);
        two.setOther(one);
        one.start();
        two.start();
    }
}

class Pedestrian extends Thread {
    private Object l;
    private Object r;
    private Pedestrian other;
    private Object current;

    Pedestrian (Object left, Object right, int firstDirection) {
        l = left;
        r = right;
        if (firstDirection==0) {
            current = l;
        }
        else {
            current = r;
        }
    }

    void setOther(Pedestrian otherP) {
        other = otherP;
    }

    Object getDirection() {
        return current;
    }

    Object getOppositeDirection() {
        if (current.equals(l)) {
            return r;
        }
        else {
            return l;
        }
    }

    void switchDirection() throws InterruptedException {
        Thread.sleep(100);
        current = getOppositeDirection();
        System.out.println(Thread.currentThread().getName() + " is stepping aside.");
    }

    public void run() {
        while (getDirection().equals(other.getDirection())) {
            try {
                switchDirection();
                Thread.sleep(100);
            } catch (InterruptedException e) {}
        }
    }
} 
PoweredByRice
quelle
2

C # -Version von Jelbourns Code:

using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace LiveLockExample
{
    static class Program
    {
        public static void Main(string[] args)
        {
            var husband = new Diner("Bob");
            var wife = new Diner("Alice");

            var s = new Spoon(husband);

            Task.WaitAll(
                Task.Run(() => husband.EatWith(s, wife)),
                Task.Run(() => wife.EatWith(s, husband))
                );
        }

        public class Spoon
        {
            public Spoon(Diner diner)
            {
                Owner = diner;
            }


            public Diner Owner { get; private set; }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public void SetOwner(Diner d) { Owner = d; }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public void Use()
            {
                Console.WriteLine("{0} has eaten!", Owner.Name);
            }
        }

        public class Diner
        {
            public Diner(string n)
            {
                Name = n;
                IsHungry = true;
            }

            public string Name { get; private set; }

            private bool IsHungry { get; set; }

            public void EatWith(Spoon spoon, Diner spouse)
            {
                while (IsHungry)
                {
                    // Don't have the spoon, so wait patiently for spouse.
                    if (spoon.Owner != this)
                    {
                        try
                        {
                            Thread.Sleep(1);
                        }
                        catch (ThreadInterruptedException e)
                        {
                        }

                        continue;
                    }

                    // If spouse is hungry, insist upon passing the spoon.
                    if (spouse.IsHungry)
                    {
                        Console.WriteLine("{0}: You eat first my darling {1}!", Name, spouse.Name);
                        spoon.SetOwner(spouse);
                        continue;
                    }

                    // Spouse wasn't hungry, so finally eat
                    spoon.Use();
                    IsHungry = false;
                    Console.WriteLine("{0}: I am stuffed, my darling {1}!", Name, spouse.Name);
                    spoon.SetOwner(spouse);
                }
            }
        }
    }
}
asostechnix
quelle
1

Ein Beispiel hierfür ist die Verwendung eines zeitgesteuerten tryLock, um mehr als eine Sperre zu erhalten. Wenn Sie nicht alle erhalten können, ziehen Sie sich zurück und versuchen Sie es erneut.

boolean tryLockAll(Collection<Lock> locks) {
  boolean grabbedAllLocks = false;
  for(int i=0; i<locks.size(); i++) {
    Lock lock = locks.get(i);
    if(!lock.tryLock(5, TimeUnit.SECONDS)) {
      grabbedAllLocks = false;

      // undo the locks I already took in reverse order
      for(int j=i-1; j >= 0; j--) {
        lock.unlock();
      }
    }
  }
}

Ich könnte mir vorstellen, dass ein solcher Code problematisch wäre, da viele Threads kollidieren und darauf warten, eine Reihe von Sperren zu erhalten. Aber ich bin mir nicht sicher, ob dies für mich als einfaches Beispiel sehr überzeugend ist.

Alex Miller
quelle
1
Damit dies ein Livelock ist, benötigen Sie einen anderen Thread, um diese Sperren in einer anderen Reihenfolge zu erhalten. Wenn alle Threads tryLockAll()mit den Sperren in locksderselben Reihenfolge verwendet werden, gibt es kein Livelock.
JaviMerino
0

Python-Version von Jelbourns Code:

import threading
import time
lock = threading.Lock()

class Spoon:
    def __init__(self, diner):
        self.owner = diner

    def setOwner(self, diner):
        with lock:
            self.owner = diner

    def use(self):
        with lock:
            "{0} has eaten".format(self.owner)

class Diner:
    def __init__(self, name):
        self.name = name
        self.hungry = True

    def eatsWith(self, spoon, spouse):
        while(self.hungry):
            if self != spoon.owner:
                time.sleep(1) # blocks thread, not process
                continue

            if spouse.hungry:
                print "{0}: you eat first, {1}".format(self.name, spouse.name)
                spoon.setOwner(spouse)
                continue

            # Spouse was not hungry, eat
            spoon.use()
            print "{0}: I'm stuffed, {1}".format(self.name, spouse.name)
            spoon.setOwner(spouse)

def main():
    husband = Diner("Bob")
    wife = Diner("Alice")
    spoon = Spoon(husband)

    t0 = threading.Thread(target=husband.eatsWith, args=(spoon, wife))
    t1 = threading.Thread(target=wife.eatsWith, args=(spoon, husband))
    t0.start()
    t1.start()
    t0.join()
    t1.join()

if __name__ == "__main__":
    main()
nflacco
quelle
Fehler: In use () wird print nicht verwendet und - was noch wichtiger ist - das hungrige Flag wird nicht auf False gesetzt.
DDR
0

Ich ändere die Antwort von @jelbourn. Wenn einer von ihnen bemerkt, dass der andere hungrig ist, sollte er (sie) den Löffel loslassen und auf eine weitere Benachrichtigung warten, damit ein Livelock passiert.

public class LiveLock {
    static class Spoon {
        Diner owner;

        public String getOwnerName() {
            return owner.getName();
        }

        public void setOwner(Diner diner) {
            this.owner = diner;
        }

        public Spoon(Diner diner) {
            this.owner = diner;
        }

        public void use() {
            System.out.println(owner.getName() + " use this spoon and finish eat.");
        }
    }

    static class Diner {
        public Diner(boolean isHungry, String name) {
            this.isHungry = isHungry;
            this.name = name;
        }

        private boolean isHungry;
        private String name;


        public String getName() {
            return name;
        }

        public void eatWith(Diner spouse, Spoon sharedSpoon) {
            try {
                synchronized (sharedSpoon) {
                    while (isHungry) {
                        while (!sharedSpoon.getOwnerName().equals(name)) {
                            sharedSpoon.wait();
                            //System.out.println("sharedSpoon belongs to" + sharedSpoon.getOwnerName())
                        }
                        if (spouse.isHungry) {
                            System.out.println(spouse.getName() + "is hungry,I should give it to him(her).");
                            sharedSpoon.setOwner(spouse);
                            sharedSpoon.notifyAll();
                        } else {
                            sharedSpoon.use();
                            sharedSpoon.setOwner(spouse);
                            isHungry = false;
                        }
                        Thread.sleep(500);
                    }
                }
            } catch (InterruptedException e) {
                System.out.println(name + " is interrupted.");
            }
        }
    }

    public static void main(String[] args) {
        final Diner husband = new Diner(true, "husband");
        final Diner wife = new Diner(true, "wife");
        final Spoon sharedSpoon = new Spoon(wife);

        Thread h = new Thread() {
            @Override
            public void run() {
                husband.eatWith(wife, sharedSpoon);
            }
        };
        h.start();

        Thread w = new Thread() {
            @Override
            public void run() {
                wife.eatWith(husband, sharedSpoon);
            }
        };
        w.start();

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        h.interrupt();
        w.interrupt();

        try {
            h.join();
            w.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Yi Zhang
quelle
0
package concurrently.deadlock;

import static java.lang.System.out;


/* This is an example of livelock */
public class Dinner {

    public static void main(String[] args) {
        Spoon spoon = new Spoon();
        Dish dish = new Dish();

        new Thread(new Husband(spoon, dish)).start();
        new Thread(new Wife(spoon, dish)).start();
    }
}


class Spoon {
    boolean isLocked;
}

class Dish {
    boolean isLocked;
}

class Husband implements Runnable {

    Spoon spoon;
    Dish dish;

    Husband(Spoon spoon, Dish dish) {
        this.spoon = spoon;
        this.dish = dish;
    }

    @Override
    public void run() {

        while (true) {
            synchronized (spoon) {
                spoon.isLocked = true;
                out.println("husband get spoon");
                try { Thread.sleep(2000); } catch (InterruptedException e) {}

                if (dish.isLocked == true) {
                    spoon.isLocked = false; // give away spoon
                    out.println("husband pass away spoon");
                    continue;
                }
                synchronized (dish) {
                    dish.isLocked = true;
                    out.println("Husband is eating!");

                }
                dish.isLocked = false;
            }
            spoon.isLocked = false;
        }
    }
}

class Wife implements Runnable {

    Spoon spoon;
    Dish dish;

    Wife(Spoon spoon, Dish dish) {
        this.spoon = spoon;
        this.dish = dish;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (dish) {
                dish.isLocked = true;
                out.println("wife get dish");
                try { Thread.sleep(2000); } catch (InterruptedException e) {}

                if (spoon.isLocked == true) {
                    dish.isLocked = false; // give away dish
                    out.println("wife pass away dish");
                    continue;
                }
                synchronized (spoon) {
                    spoon.isLocked = true;
                    out.println("Wife is eating!");

                }
                spoon.isLocked = false;
            }
            dish.isLocked = false;
        }
    }
}
Athanasios V.
quelle