Java 线程在增加线程数的同时投射出奇怪的行为



我创建了一个用于研究目的的项目,该项目使用线程模拟餐厅服务。有一个供厨师准备餐点的线程和另一个供服务员服务的线程来提供餐点。当我用 1 名厨师和 5 名服务员进行测试时,它工作正常。但是当我增加厨师的数量时,程序会无限期地运行。怎么了?这是代码:

类主

package restaurant;
import java.util.concurrent.Semaphore;
public class Main {
    public static int MAX_NUM_MEALS = 5;
    public static int OLDEST_MEAL = 0;
    public static int NEWEST_MEAL = -1;
    public static int DONE_MEALS = 0;
    public static int NUM_OF_COOKS = 1;
    public static int NUM_OF_WAITERS = 5;
    public static Semaphore mutex = new Semaphore(1);
    static Cook cookThreads[] = new Cook[NUM_OF_COOKS];
    static Waiter waiterThreads[] = new Waiter[NUM_OF_WAITERS];
    public static void main(String[] args) {
        for(int i = 0; i < NUM_OF_COOKS; i++) {
            cookThreads[i] = new Cook(i);
            cookThreads[i].start();
        }
        for(int i = 0; i < NUM_OF_WAITERS; i++) {
            waiterThreads[i] = new Waiter(i);
            waiterThreads[i].start();
        }
        try {
            for(int i = 0; i < NUM_OF_COOKS; i++) {
                cookThreads[i].join();
            }
            for(int i = 0; i < NUM_OF_WAITERS; i++) {
                waiterThreads[i].join();
            }
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All done");
    }

}

班级厨师

package restaurant;
public class Cook extends Thread{
    private int id;
    public Cook(int id) {
        this.id = id;
    }
    public void run() {
        while(true) {
            System.out.println("Cook " + id + " is prepearing meal");
            try {
                Thread.sleep(1000);
                Main.mutex.acquire();
                Main.NEWEST_MEAL++;
                Main.mutex.release();
                Main.mutex.acquire();
                Main.DONE_MEALS++;
                Main.mutex.release();
                System.out.println("Cook " + id + " has finished the meal");
                if(Main.DONE_MEALS == 5) {
                    System.out.println("Cook " + id + " has finished his job");
                    break;
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

班服员

package restaurant;
public class Waiter extends Thread{
    private int id;
    public Waiter(int id) {
        this.id = id;
    }
    public void run() {
        while(true) {
            System.out.println("Waiter " + id + " will check if there is any meal to serve");
            if(Main.NEWEST_MEAL >= Main.OLDEST_MEAL) {
                try {
                    Main.mutex.acquire();
                    Main.OLDEST_MEAL++;
                    Main.mutex.release();
                    System.out.println("Waiter " + id + " is picking up meal");
                    Thread.sleep(500);
                    System.out.println("Waiter " + id + " has delivered the meal to client");                   
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(Main.DONE_MEALS == 5) {
                System.out.println("Waiter " + id + " has finished his job");
                break;
            }
            System.out.println("No meal to serve. Waiter " + id + " will come back later");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

两个问题:

  1. 因为你有两个厨师,你的一个厨师可能不会看到Main.DONE_MEALS == 5。由于另一个厨师,它会从 4 跳到 6。相反,请检查Main.DONE_MEALS >= 5
  2. 不能保证厨师或服务员线程会看到Main.DONE_MEALS更新。相反,请考虑使用private static final AtomicInteger字段。AtomicInteger 类是一个线程安全的整数实现,它使其他线程能够以线程安全的方式查看它。

传统的解决方法是:

a) 您不仅在写作时必须使用锁(互斥锁),还必须在阅读时使用锁 (mutex) - 否则它将无法正常工作。想象一下,你同意了一个信号来指示浴室是否忙,但有些人只是决定忽略它 - 行不通!

b) 在做某事之前检查条件。
一旦你拿到了锁,你就不知道状态了,所以你应该先检查一下,然后再做另一顿饭。如果你首先检查是否已经有 5 个完成的膳食,并且只在还没有 5 个时生产膳食,它应该可以解决这个问题,并且你应该只看到done_meals <= 5(你应该查看代码的其他部分,因为它有类似的问题,虽然)。

就像其他人提到的那样,有更干净的方法来编写这个,但 IMO 你的代码非常适合练习和理解,所以我会尝试一下,而不是跳到像 AtomicInteger 这样的东西。

最新更新