Contoh livelock yang bagus?


141

Saya mengerti apa itu livelock, tapi saya bertanya-tanya apakah ada yang punya contoh berbasis kode yang bagus? Dan berdasarkan kode, saya tidak bermaksud "dua orang mencoba untuk melewati satu sama lain di koridor". Jika saya membacanya lagi, saya akan kehilangan makan siang.


96
Bagaimana dengan simulasi perangkat lunak dua orang yang mencoba melewati satu sama lain di koridor?
1800 INFORMASI

36
Terkutuklah kamu! Saya kehilangan makan siang!
Alex Miller


Lelucon terkait untuk orang-orang yang ingin tahu: codingarchitect.wordpress.com/2006/01/18/…
Jorjon

4
Dua orang yang berusaha melewati satu sama lain di koridor: gist.github.com/deepankarb/d2dd6f21bc49902376e614d3746b8965 : p
iceman

Jawaban:


119

Berikut adalah contoh livelock Jawa yang sangat sederhana di mana suami dan istri mencoba makan sup, tetapi hanya memiliki satu sendok di antara mereka. Setiap pasangan terlalu sopan, dan akan melewati sendok jika yang lain belum makan.

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
Tidakkah getOwnermetode harus disinkronkan juga? Dari Java Efektif " sinkronisasi tidak berpengaruh kecuali baca dan tulis ".
Sanghyun Lee

Tidakkah seharusnya dia menggunakan Thread.join()daripada Thread.sleep(), karena dia ingin menunggu pasangannya makan?
Solace

apa yang harus kita lakukan untuk mengatasi masalah livelock dalam contoh khusus ini?
Thor

1
The getOwnerMetode harus disinkronkan karena bahkan jika setOwnerdisinkronkan, ini tidak menjamin benang menggunakan getOwner(atau mengakses lapangan ownerlangsung) akan melihat perubahan yang dilakukan oleh thread lain melakukan setOwner. Vid ini menjelaskan hal ini dengan sangat hati-hati: youtube.com/watch?v=WTVooKLLVT8
Timofey

2
Anda tidak perlu menggunakan synchronized kata kunci untuk setOwnermetode, karena membaca dan menulis adalah tindakan atom untuk variabel referensi.
atiqkhaled

75

Selain komentar Flippant, salah satu contoh yang diketahui muncul adalah dalam kode yang mencoba mendeteksi dan menangani situasi deadlock. Jika dua utas mendeteksi kebuntuan, dan mencoba untuk "melangkah ke samping" untuk satu sama lain, tanpa peduli mereka akan terjebak dalam lingkaran selalu "melangkah ke samping" dan tidak pernah berhasil bergerak maju.

"Minggir" Maksudku, mereka akan melepaskan kunci dan mencoba membiarkan yang lain mendapatkannya. Kita mungkin membayangkan situasi dengan dua utas melakukan ini (kodesemu):

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

Di samping kondisi balapan, yang kita miliki di sini adalah situasi di mana kedua utas, jika mereka masuk pada saat yang sama, akan berakhir berjalan di loop dalam tanpa melanjutkan. Jelas ini adalah contoh yang disederhanakan. Sebuah perbaikan naif adalah dengan menempatkan semacam keacakan dalam jumlah waktu thread akan menunggu.

Perbaikan yang tepat adalah untuk selalu menghormati hirarki kunci . Pilih pesanan di mana Anda memperoleh kunci dan menaatinya. Misalnya jika kedua utas selalu mendapatkan kunci1 sebelum kunci2, maka tidak ada kemungkinan kebuntuan.


Ya, saya mengerti itu. Saya mencari contoh kode aktual semacam itu. Pertanyaannya adalah apa arti "menyingkir" dan bagaimana ia menghasilkan skenario seperti itu.
Alex Miller

Saya mengerti bahwa ini adalah contoh yang dibuat-buat, tetapi apakah ini bisa mengarah pada livelock? Bukankah akan jauh lebih mungkin bahwa pada akhirnya sebuah jendela akan terbuka di mana satu fungsi dapat mengambil keduanya, karena ketidakkonsistenan dalam waktu thread-thread tersebut keras untuk dijalankan dan ketika mereka dijadwalkan.
DubiousPusher

Meskipun itu bukan livelock yang stabil karena mereka pada akhirnya akan keluar darinya, saya pikir itu cocok dengan deskripsi yang cukup baik
1800 INFORMASI

Contoh yang sangat bagus dan bermakna.
alecov

7

Karena tidak ada jawaban yang ditandai sebagai jawaban yang diterima, saya telah berupaya membuat contoh kunci langsung;

Program asli ditulis oleh saya pada bulan April 2012 untuk mempelajari berbagai konsep multithreading. Kali ini saya telah memodifikasinya untuk membuat jalan buntu, kondisi balapan, livelock dll.

Jadi mari kita memahami pernyataan masalah terlebih dahulu;

Masalah Pembuat Cookie

Ada beberapa wadah bahan: ChocoPowederContainer , WheatPowderContainer .CookieMaker mengambil sejumlah bubuk dari wadah bahan untuk membuat kue . Jika pembuat kue menemukan wadah kosong, ia memeriksa wadah lain untuk menghemat waktu. Dan menunggu sampai Filler mengisi wadah yang diperlukan. Ada Pengisi yang memeriksa wadah pada interval reguler dan mengisi jumlah tertentu jika wadah membutuhkannya.

Silakan periksa kode lengkap di github ;

Biarkan saya menjelaskan implementasi Anda secara singkat.

  • Saya memulai Filler sebagai utas daemon. Jadi itu akan tetap mengisi wadah secara berkala. Untuk mengisi wadah pertama, ia mengunci wadah -> memeriksa apakah perlu bubuk -> mengisinya -> memberi sinyal semua pembuat yang menunggu -> membuka kunci wadah.
  • Saya buat CookieMaker dan mengatur bahwa itu dapat memanggang hingga 8 cookie secara paralel. Dan saya mulai 8 utas untuk memanggang kue.
  • Setiap thread pembuat membuat 2 sub-thread yang dapat dipanggil untuk mengambil bubuk dari wadah.
  • Sub-thread mengambil kunci pada wadah dan memeriksa apakah itu memiliki cukup bubuk. Jika tidak, tunggu beberapa saat. Setelah Filler mengisi wadah, ia mengambil bubuk, dan membuka kunci wadah.
  • Sekarang melengkapi kegiatan lain seperti: membuat campuran dan memanggang dll.

Mari kita lihat kodenya:

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

Semuanya berjalan dengan baik sampai Filler mengisi wadah. Tetapi jika saya lupa untuk memulai pengisi, atau pengisi pergi cuti yang tak terduga, sub-utas terus mengubah negara mereka untuk memungkinkan pembuat lain untuk pergi dan memeriksa wadah.

Saya juga telah membuat daemon ThreadTracer yang terus memantau status utas dan kebuntuan. Ini keluaran dari konsol;

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]

Anda akan melihat bahwa sub-utas dan mengubah negara mereka dan menunggu.


4

Contoh nyata (walaupun tanpa kode persis) adalah dua proses yang saling bersaing mengunci secara langsung dalam upaya untuk memperbaiki kebuntuan SQL server, dengan setiap proses menggunakan algoritma menunggu-coba yang sama untuk mencoba kembali. Meskipun ini adalah keberuntungan dari pengaturan waktu, saya telah melihat ini terjadi pada mesin yang terpisah dengan karakteristik kinerja yang serupa dalam menanggapi pesan yang ditambahkan ke topik EMS (misalnya menyimpan pembaruan grafik objek tunggal lebih dari sekali), dan tidak dapat mengontrol urutan kunci.

Solusi yang baik dalam hal ini adalah memiliki konsumen yang bersaing (mencegah pemrosesan duplikat setinggi mungkin dalam rantai dengan mempartisi pekerjaan pada objek yang tidak terkait).

Solusi yang kurang diinginkan (ok, hack-kotor) adalah untuk memecah waktu nasib buruk (jenis perbedaan kekuatan dalam pemrosesan) di muka atau memecahnya setelah kebuntuan dengan menggunakan algoritma yang berbeda atau beberapa elemen keacakan. Ini masih bisa memiliki masalah karena kemungkinan urutan pengambilannya "lengket" untuk setiap proses, dan ini membutuhkan waktu minimum tertentu yang tidak diperhitungkan dalam menunggu-coba lagi.

Namun solusi lain (setidaknya untuk SQL Server) adalah mencoba tingkat isolasi yang berbeda (misalnya snapshot).


2

Saya membuat kode contoh 2 orang yang lewat di koridor. Kedua utas akan saling menghindari segera setelah mereka menyadari arah mereka sama.

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

C # versi kode jelbourn:

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

Salah satu contoh di sini mungkin menggunakan tryLock berjangka waktu untuk mendapatkan lebih dari satu kunci dan jika Anda tidak dapat memperoleh semuanya, mundur dan coba lagi.

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

Saya bisa membayangkan kode seperti itu akan bermasalah karena Anda memiliki banyak utas bertabrakan dan menunggu untuk mendapatkan satu set kunci. Tapi saya tidak yakin ini sangat menarik bagi saya sebagai contoh sederhana.


1
untuk ini menjadi livelock Anda perlu utas lainnya untuk mendapatkan kunci itu dalam urutan yang berbeda. Jika semua utas digunakan tryLockAll()dengan kunci dalam locksurutan yang sama, tidak ada livelock.
JaviMerino

0

Versi python dari kode jelbourn:

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

Bug: digunakan (), cetakan tidak digunakan dan - yang lebih penting - bendera lapar tidak disetel ke False.
GDR

0

Saya memodifikasi jawaban @jelbourn. Ketika salah satu dari mereka memperhatikan bahwa yang lain lapar, dia harus melepaskan sendok dan menunggu pemberitahuan lainnya, sehingga livelock terjadi.

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;
        }
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.