Ví dụ điển hình của livelock?


141

Tôi hiểu livelock là gì, nhưng tôi đã tự hỏi liệu có ai có một ví dụ dựa trên mã tốt về nó không? Và theo mã, tôi không có nghĩa là "hai người cố gắng vượt qua nhau trong một hành lang". Nếu tôi đọc lại lần nữa, tôi sẽ mất bữa trưa.


96
Làm thế nào về một mô phỏng phần mềm của hai người cố gắng vượt qua nhau trong một hành lang?
1800 THÔNG TIN

36
Nguyền rủa bạn! Tôi bị mất bữa trưa!
Alex Miller

3
Thích hợp lạ lùng: seuss.wikia.com/wiki/The_Zax
NotMe

Trò đùa liên quan dành cho những người
Jorjon

4
Hai người cố gắng vượt qua nhau trong một hành lang: gist.github.com/deepankarb/d2dd6f21bc49902376e614d3746b8965 : p
iceman

Câu trả lời:


119

Đây là một ví dụ Java rất đơn giản về livelock nơi chồng và vợ đang cố ăn súp, nhưng chỉ có một muỗng giữa chúng. Mỗi người phối ngẫu quá lịch sự, và sẽ chuyền thìa nếu người kia chưa ăn.

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
Không phải getOwnerphương pháp cũng phải được đồng bộ hóa sao? Từ Java hiệu quả " đồng bộ hóa không có hiệu lực trừ khi cả đọc và viết ".
Sanghyun Lee

Anh ta không nên sử dụng Thread.join()chứ không phải Thread.sleep()vì anh ta muốn đợi người phối ngẫu ăn?
Solace

Chúng ta nên làm gì để khắc phục vấn đề của livelock trong ví dụ cụ thể này?
Thor

1
Các getOwnerphương pháp phải đồng bộ từ ngay cả khi setOwnerđược đồng bộ, điều này không đảm bảo các chủ đề sử dụng getOwner(hoặc truy cập vào lĩnh vực này ownertrực tiếp) sẽ thấy những thay đổi được thực hiện bởi các chủ đề khác thực hiện setOwner. Vid này giải thích điều này rất cẩn thận: youtube.com/watch?v=WTVooKLLVT8
Timofey

2
Bạn không cần sử dụng synchronized từ khóa cho setOwnerphương thức, vì đọc và viết là hành động nguyên tử cho biến tham chiếu.
atiqkhaled

75

Bỏ qua các bình luận, một ví dụ được biết là xuất hiện trong mã cố gắng phát hiện và xử lý các tình huống bế tắc. Nếu hai luồng phát hiện ra bế tắc và cố gắng "bước sang một bên" cho nhau, thì không cần quan tâm, cuối cùng họ sẽ bị mắc kẹt trong một vòng lặp luôn "bước sang một bên" và không bao giờ xoay sở để tiến lên.

Bằng cách "bước sang một bên" Tôi có nghĩa là họ sẽ giải phóng khóa và cố gắng để người khác có được nó. Chúng ta có thể tưởng tượng tình huống với hai luồng làm việc này (mã giả):

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

Điều kiện cuộc đua sang một bên, những gì chúng ta có ở đây là một tình huống trong đó cả hai luồng, nếu chúng nhập cùng một lúc sẽ kết thúc chạy trong vòng lặp bên trong mà không tiếp tục. Rõ ràng đây là một ví dụ đơn giản. Một sửa chữa ngây thơ sẽ là đặt một số loại ngẫu nhiên trong số lượng thời gian các chủ đề sẽ chờ đợi.

Cách khắc phục thích hợp là luôn tôn trọng quyền thừa kế khóa . Chọn một thứ tự mà bạn có được các ổ khóa và dính vào đó. Ví dụ: nếu cả hai luồng luôn thu được lock1 trước lock2, thì không có khả năng bế tắc.


Vâng, tôi hiểu điều đó. Tôi đang tìm kiếm một ví dụ mã thực tế như vậy. Câu hỏi đặt ra là "bước sang một bên" nghĩa là gì và làm thế nào để tạo ra một kịch bản như vậy.
Alex Miller

Tôi nhận ra rằng đây là một ví dụ giả định nhưng có khả năng điều này có thể dẫn đến một bản nhạc không? Sẽ không có nhiều khả năng cuối cùng một cửa sổ sẽ mở ra khi một chức năng có thể lấy cả hai, do sự không nhất quán trong thời gian các luồng được chạy và khi chúng được lên lịch.
DubiousPizer

Mặc dù nó không phải là một bản nhạc ổn định vì cuối cùng chúng sẽ thoát ra khỏi nó, nhưng tôi nghĩ rằng nó phù hợp với mô tả đủ tốt
1800 THÔNG TIN

Ví dụ tuyệt vời và ý nghĩa.
alecov

7

Vì không có câu trả lời nào được đánh dấu là câu trả lời được chấp nhận, tôi đã cố gắng tạo ví dụ khóa trực tiếp;

Chương trình gốc được tôi viết vào tháng 4 năm 2012 để tìm hiểu các khái niệm khác nhau về đa luồng. Lần này tôi đã sửa đổi nó để tạo ra bế tắc, điều kiện cuộc đua, livelock, v.v.

Vì vậy, hãy hiểu tuyên bố vấn đề đầu tiên;

Vấn đề về trình tạo cookie

Có một số container thành phần: ChocoPowederContainer , WheatPowderContainer . CookieMaker lấy một lượng bột từ hộp đựng nguyên liệu để nướng Cookie . Nếu một nhà sản xuất cookie tìm thấy một container rỗng, nó sẽ kiểm tra một container khác để tiết kiệm thời gian. Và đợi cho đến khi Filler lấp đầy container cần thiết. Có một Filler kiểm tra container theo định kỳ và điền một số lượng nếu một container cần nó.

Vui lòng kiểm tra mã hoàn chỉnh trên github ;

Hãy để tôi giải thích cho bạn thực hiện ngắn gọn.

  • Tôi bắt đầu Filler như chủ đề daemon. Vì vậy, nó sẽ tiếp tục làm đầy container theo định kỳ. Để lấp đầy một container trước tiên, nó khóa container -> kiểm tra xem nó có cần một ít bột không -> đổ đầy nó -> báo hiệu cho tất cả các nhà sản xuất đang chờ nó -> mở khóa container.
  • Tôi tạo CookieMaker và thiết lập rằng nó có thể nướng tới 8 cookie song song. Và tôi bắt đầu 8 chủ đề để nướng bánh quy.
  • Mỗi luồng của nhà sản xuất tạo ra 2 luồng con có thể gọi được để lấy bột từ các thùng chứa.
  • tiểu luồng lấy một khóa trên một thùng chứa và kiểm tra xem nó có đủ bột không. Nếu không, hãy đợi một thời gian. Khi Filler đổ đầy thùng chứa, nó sẽ lấy bột và mở khóa hộp chứa.
  • Bây giờ nó hoàn thành các hoạt động khác như: làm hỗn hợp và nướng, vv

Chúng ta hãy xem 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();
        :
}

Thành phầnContainer.java

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

Tất cả mọi thứ chạy tốt cho đến khi Filler đang lấp đầy các container. Nhưng nếu tôi quên khởi động filler hoặc filler sẽ nghỉ đột xuất, các tiểu trình tiếp tục thay đổi trạng thái của chúng để cho phép nhà sản xuất khác đi và kiểm tra container.

Tôi cũng đã tạo một ThreadTracer daemon để theo dõi trạng thái luồng và khóa chết. Đây là đầu ra từ bàn điều khiển;

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]

Bạn sẽ nhận thấy rằng các chủ đề phụ và thay đổi trạng thái của họ và chờ đợi.


4

Một ví dụ thực tế (mặc dù không có mã chính xác) là hai quy trình cạnh tranh khóa trực tiếp trong nỗ lực sửa lỗi cho bế tắc máy chủ SQL, với mỗi quy trình sử dụng cùng một thuật toán thử lại để thử lại. Mặc dù đó là sự may mắn của thời gian, tôi đã thấy điều này xảy ra trên các máy riêng biệt có đặc điểm hiệu suất tương tự để phản hồi một thông báo được thêm vào chủ đề EMS (ví dụ: lưu một bản cập nhật của một đồ thị đối tượng nhiều lần) và không thể kiểm soát thứ tự khóa.

Một giải pháp tốt trong này trường hợp sẽ có người tiêu dùng cạnh tranh (ngăn chặn xử lý trùng lặp như lên cao trong chuỗi càng tốt bằng cách phân vùng làm việc trên các đối tượng liên quan).

Một giải pháp ít mong muốn hơn (ok, bẩn-hack) là phá vỡ sự xui xẻo về thời gian (loại khác biệt về lực trong xử lý) trước hoặc phá vỡ nó sau bế tắc bằng cách sử dụng các thuật toán khác nhau hoặc một số yếu tố ngẫu nhiên. Điều này vẫn có thể có vấn đề vì có thể thứ tự khóa bị "dính" cho mỗi quy trình và điều này mất một khoảng thời gian tối thiểu nhất định không được tính trong thử lại.

Tuy nhiên, một giải pháp khác (ít nhất là đối với SQL Server) là thử một mức cô lập khác (ví dụ: ảnh chụp nhanh).


2

Tôi đã mã hóa ví dụ về 2 người đi qua trong một hành lang. Hai luồng sẽ tránh nhau ngay khi họ nhận ra hướng đi của mình giống nhau.

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

Phiên bản C # của mã 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

Một ví dụ ở đây có thể là sử dụng một tryLock được định thời gian để có được nhiều hơn một khóa và nếu bạn không thể có được tất cả, hãy lùi lại và thử lại.

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

Tôi có thể tưởng tượng mã như vậy sẽ có vấn đề khi bạn có rất nhiều luồng va chạm và chờ đợi để có được một bộ khóa. Nhưng tôi không chắc đây là một ví dụ đơn giản.


1
để đây là một bản nhạc nền, bạn sẽ cần một chủ đề khác để có được các khóa đó theo một thứ tự khác. Nếu tất cả các luồng sử dụng tryLockAll()với các khóa theo lockscùng một thứ tự, thì không có livelock.
JaviMerino

0

Phiên bản Python của mã 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()

Lỗi: in use (), in không được sử dụng và - quan trọng hơn - cờ đói không được đặt thành Sai.
GDR

0

Tôi sửa đổi câu trả lời của @jelbourn. Khi một trong số họ thông báo rằng người kia đang đói, anh ấy (cô ấy) nên nhả cái muỗng ra và đợi một thông báo khác, vì vậy một cuộc sống chung xảy ra.

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;
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.