简体   繁体   中英

Java thread project weird behavior while increasing num of threads

I created a project for studying purposes that simulates a restaurant service using Threads. There is a Thread for Cook(s) to prepare a meal and another Thread for Waiter(s) to serve the meal. When I tested it with 1 cook and 5 waiters, it worked fine. But when I increase the number of cooks, the program runs indefinitely. What is wrong? Here is the code:

Class Main

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

    }


}

Class Cook

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

Class Waiter

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

Two issues:

  1. Because you have two cooks, one of your cooks likely won't see Main.DONE_MEALS == 5 . It will jump from 4 to 6 because of the other cook. Instead, check for Main.DONE_MEALS >= 5 .
  2. There is no guarantee that the cook or waiter threads will see the updates to Main.DONE_MEALS . Instead, consider having a private static final AtomicInteger field. The AtomicInteger class is a thread-safe integer implementation that enables other threads to see it in a thread-safe way.

The traditional fix would be:

a) You have to use the lock (mutex) not only when you write, but also when you read - otherwise it won't work correctly. Just imagine you agreed on a signal to indicate if the bathroom is busy, but some just decide to ignore it - won't work!.

b) Check the condition before you do something.
Once you acquire the lock, you don't know the state so you should first check it before you proceed to make another meal. If you first check if there are already 5 done meals and only produce meals if there aren't yet 5, it should fix this problem, and you should only ever see done_meals <= 5 (you should review other parts of the code because it has similar problems, though).

Like others have mentioned, there are cleaner ways to write this but IMO your code is very suited for practice and understanding, so I'd try that rather than jumping for things like AtomicInteger.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM