Canlı kilit için iyi bir örnek?


141

Livelock nedir anlıyorum, ama kimse iyi bir kod tabanlı bir örnek olup olmadığını merak ediyordum? Ve tarafından ben do kod tabanlı değil "bir koridorda birbirlerine geçmiş almaya çalışırken iki kişi" anlamına gelir. Tekrar okursam öğle yemeğimi kaybederim.


96
Bir koridorda birbirini geçmeye çalışan iki kişinin yazılım simülasyonuna ne dersiniz?
1800 BİLGİ

36
Lanet olsun sana! Öğle yemeğimi kaybettim!
Alex Miller

3
Garip bir şekilde uygun: seuss.wikia.com/wiki/The_Zax
NotMe

Meraklı arkadaşlar için ilgili şaka: codingarchitect.wordpress.com/2006/01/18/…
Jorjon

4
Bir koridorda birbirini geçmeye çalışan iki kişi: gist.github.com/deepankarb/d2dd6f21bc49902376e614d3746b8965 : p
iceman

Yanıtlar:


119

İşte bir koca ve eşin çorba yemeye çalıştığı çok basit bir livelock Java örneği, ancak aralarında sadece bir kaşık var. Her eş çok kibar ve diğeri henüz yememişse kaşığı geçecektir.

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();
    }
}

6
Does not getOwneryöntem sıra eşitlenmesi gerekir? Etkili Java'dan " hem okuma hem de yazma dışında senkronizasyonun hiçbir etkisi yoktur ".
Sanghyun Lee

Eşinden yemek yemesini beklemek istediği için kullanmak Thread.join()yerine kullanmamalı Thread.sleep()mı?
Solace

bu örnekte livelock sorununun üstesinden gelmek için ne yapmalıyız?
Thor

1
getOwnerBile bu yana yöntem senkronize edilmelidir setOwnersenkronize edilir, bu kullanarak iplik garanti etmez getOwner(veya alan erişen ownergerçekleştirerek diğer parçacığı tarafından yapılan değişiklikleri görür doğrudan) setOwner. Bu vid bunu çok dikkatli açıklıyor: youtube.com/watch?v=WTVooKLLVT8
Timofey

2
Yöntem synchronized için anahtar kelime kullanmanıza gerek yoktur setOwner, çünkü okuma ve yazma referans değişkeni için atom eylemidir.
atiqkhaled

75

Flippant yorumları bir yana, ortaya çıktığı bilinen bir örnek, kilitlenme durumlarını algılamaya ve ele almaya çalışan koddadır. İki iş parçacığı bir kilitlenme algılarsa ve birbirleri için "kenara çekilmeye" çalışırlarsa, dikkatsiz bir döngü içinde her zaman "kenara çekilir" ve asla ileri doğru hareket etmeyi başaramazlar.

"Kenara çekil" derken kilidi serbest bırakacaklar ve diğerinin kilidini almasına izin vereceklerdir. Bunu iki iş parçacığının (pseudocode) yaptığı bir durum düşünebiliriz:

// 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();
}

Yarış koşulları bir yana, burada sahip olduğumuz şey, her iki ipliğin de aynı anda girmeleri durumunda, devam etmeden iç döngüde koşacakları bir durumdur. Açıkçası bu basitleştirilmiş bir örnektir. Naif bir düzeltme, iş parçacıklarının bekleyeceği süreye bir çeşit rastgelelik koymak olacaktır.

Doğru düzeltme her zaman kilit heirarşiye saygı göstermektir . Kilitleri aldığınız bir sipariş seçin ve buna uyun. Örneğin, her iki iş parçacığı da her zaman lock2'den önce lock1 alırsa, kilitlenme olasılığı yoktur.


Evet, anlıyorum. Böyle bir gerçek kod örneği arıyorum. Soru "kenara çekilmek" ne demek ve böyle bir senaryoyu nasıl ürettiği.
Alex Miller

Bunun anlaşmalı bir örnek olduğunu anlıyorum, ancak bunun bir livelock'a yol açması muhtemel mi? Sonunda, bir işlevin her ikisini birden alabileceği bir pencerenin açılmasının, iş parçacıklarının yüksek sesle çalıştığı zaman ve planlanan zamandaki tutarsızlıklar nedeniyle açılma olasılığı daha yüksek olmaz.
DubiousPusher

Her ne kadar sonunda ortadan kalkacakları için istikrarlı bir livelock olmasa da, açıklamaya yeterince iyi uyduğunu düşünüyorum
1800 BİLGİ

Mükemmel ve anlamlı bir örnek.
alecov

7

Kabul edilen cevap olarak işaretlenmiş bir cevap olmadığından, canlı kilit örneği oluşturmaya çalıştım;

Özgün program , çeşitli çoklu kullanım kavramlarını öğrenmek için Nisan 2012'de yazdı. Bu sefer kilitlenme, yarış durumu, canlı kilit vb.

Önce sorun ifadesini anlayalım;

Çerez Oluşturma Sorunu

Bazı içerik kapları vardır: ChocoPowederContainer , WheatPowderContainer . CookieMaker bir Cookie pişirmek için bileşen kaplarından bir miktar toz alır . Bir çerez üreticisi bir kap boş bulursa, zaman kazanmak için başka bir kap olup olmadığını kontrol eder. Ve Filler gerekli kabı doldurana kadar bekler . Konteyneri düzenli aralıklarla kontrol eden ve bir konteynere ihtiyacı varsa bir miktar dolduran bir Dolgu vardır .

Lütfen github'daki kodun tamamını kontrol edin ;

Kısaca uygulamanızı açıklayayım.

  • Filler'i daemon thread olarak başlatıyorum . Böylece konteynırları düzenli aralıklarla doldurmaya devam edecektir. Bir konteyneri doldurmak için önce konteyneri kilitler -> biraz toz gerekip gerekmediğini kontrol edin -> doldurur -> bekleyen tüm üreticilere sinyal verin -> konteynerin kilidini açın.
  • CookieMaker oluşturdum ve paralel olarak en fazla 8 çerez pişirebileceğini ayarladım. Ve kurabiye pişirmek için 8 konu başlıyorum.
  • Her üretici ipliği, kaplardan toz almak için 2 çağrılabilir alt iplik oluşturur.
  • alt iplik kabı kilitler ve yeterli toz olup olmadığını kontrol eder. Değilse, bir süre bekleyin. Dolgu kabı doldurduktan sonra tozu alır ve kabın kilidini açar.
  • Şimdi karışım ve fırın gibi diğer faaliyetleri tamamlıyor.

Kodda bir göz atalım:

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;
}

Filler kapları doldurana kadar her şey yolunda gider . Ancak dolguyu başlatmayı unutursam veya dolgu beklenmedik bir izin verirse, alt iplikler diğer üreticinin gidip konteyneri kontrol etmesine izin vermek için durumlarını değiştirmeye devam eder.

Ayrıca iş parçacığı durumları ve kilitlenmeleri izleyen bir daemon ThreadTracer oluşturduk. Bu konsoldan çıktı;

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]

Alt konuları ve durumlarını değiştirip beklediğini göreceksiniz.


4

Gerçek (kesin kod olmasa da) bir örnek, SQL Server kilitlenmesini düzeltmek için iki rakip işlemin canlı kilitlenmesidir ve her işlem yeniden denemek için aynı bekleme-yeniden deneme algoritmasını kullanır. Zamanlama şansı olsa da, bunun bir EMS konusuna eklenen bir mesaja (örneğin tek bir nesne grafiğinin bir kereden fazla kaydedilmesi) ve kontrol edememesine benzer performans özelliklerine sahip ayrı makinelerde gerçekleştiğini gördüm kilit sırası.

Bu durumda iyi bir çözüm, rakip tüketicilere sahip olmak olacaktır (ilgisiz nesneler üzerinde çalışmayı bölümlere ayırarak zincirde yinelenen işlemeyi olabildiğince yukarı doğru önlemek).

Daha az arzu edilen (tamam, kirli-hack) bir çözüm, zamanlama kötü şansını (işlemdeki kuvvet farklılıkları) önceden kırmak veya farklı algoritmalar veya rastgele bir öğe kullanarak kilitlenme sonrasında kırmaktır. Bu hala sorunlara neden olabilir çünkü kilit alma emri her işlem için "yapışkan" dır ve bu, yeniden deneme işleminde hesaba katılmayan belirli bir minimum zaman alır.

Yine başka bir çözüm (en azından SQL Server için) farklı bir yalıtım seviyesi denemektir (örn. Anlık görüntü).


2

Bir koridordan geçen 2 kişinin örneğini kodladım. İki iplik, yönlerinin aynı olduğunu fark eder etmez birbirlerinden kaçınırlar.

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) {}
        }
    }
} 

2

Jelbourn kodunun C # sürümü:

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);
                }
            }
        }
    }
}

1

Buradaki bir örnek, birden fazla kilit almak için zamanlanmış bir tryLock kullanıyor olabilir ve hepsini alamıyorsanız, geri çekilip tekrar deneyin.

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();
      }
    }
  }
}

Çarpışma ve kilitleri bir dizi elde etmek için bekleyen iş parçacıkları çok var gibi böyle bir kod sorunlu olacağını hayal edebiliyorum. Ama bunun basit bir örnek olarak bana çok cazip geldiğinden emin değilim.


1
bunun bir livelock olması için, bu kilitleri farklı bir sırayla elde etmek için başka bir iş parçacığına ihtiyacınız olacak. Tüm dişler tryLockAll()kilitlerle locksaynı sırada kullanılırsa, livelock yoktur.
JaviMerino

0

Jelbourn kodunun Python sürümü:

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()

Hatalar: kullanımda (), baskı kullanılmıyor ve daha da önemlisi aç bayrak False olarak ayarlanmadı.
GDR

0

@Jelbourn cevabını değiştirdim. Biri diğerinin aç olduğunu fark ettiğinde, kaşığı serbest bırakmalı ve başka bir bildirim beklemelidir, bu yüzden bir livelock olur.

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();
        }
    }
}

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;
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.