简体   繁体   中英

wait and notifyall waiting for other thread

I am doing the chef, bread, and customer scenario in Java with thread. So basically the chef makes a bread, the customer eats it, the chef makes more. The maximum is 20. The chef stop making bread when there are 20. The customer stop eating when there is none left. But everytime I use notifyall, it wait four seconds before the customer eat it(supposed to make 3 more breads). Here is the code for run in the Chef class(implements runnable)

public void run(){
    int id = 0;
    while(true){
        if(Basket.breadList.size() == 20){
            synchronized(Basket.breadList){
                try {
                    Basket.breadList.wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

        Bread bread = new Bread(id);

        System.out.println("Bread " + id + " had just been made. ");
        synchronized(Basket.breadList){
            Basket.breadList.notifyAll();
            try {
                Thread.sleep(1000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            id++;
        }


    }
}

Here is the code for Customer:

public void run(){
    int id;
    while(true){
        if(Basket.breadList.size() == 0){
            synchronized(Basket.breadList){
                try {
                    Basket.breadList.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        id = Basket.breadList.get(Basket.breadList.size()-1).id;
        Basket.breadList.remove(Basket.breadList.size()-1);
        System.out.println("Bread " + id + " had just been eaten. ");
        synchronized(Basket.breadList){
            Basket.breadList.notifyAll();
            try {
                Thread.sleep(4000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }



    }

}

Here is the code in the controller:

public static void main(String[] args) {
    Chef chef = new Chef();
    Customer customer = new Customer();
    Thread t1 = new Thread(chef);
    Thread t2 = new Thread(customer);
    t1.start();

    try {
        Thread.sleep(20);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    t2.start();

}

Basket.breadList is just an arraylist of breads.

please help. Much appreciated!

After your Customer eats any bread whatsoever, it always waits 4 seconds. There is nothing to prevent this wait (typically some if () should be in place).

Rule: never sleep() unconditionally , unless you absolutely sure this is how it is supposed to be. You always sleep() because there is nothing else to accomplish and probably will not be for a while. So you need to check if there is.

Also, notifyAll() is typically done immediately after some prior action that makes things available for other threads to process.
Rule: call notifyAll() on a container immediately after you put something in it.

It is also not clear, in which code and at what time Chef adds the Bread to the basket. I assume bread adds itself in its own constructor - if so, it is an anti-pattern. Keep bread simple and healthy, it will taste better this way. Make Chef do the work. I would freak out if, while kneading and baking itself bread crawls into the basket.

Generally speaking, try to write your code exactly as actors in real world would act. Would Chef notify customer basket is not empty? When? Would customer notify Chef when basket is empty? When? When does either of them wait?

Your code is not locking effectively, so that while one thread is working the other can tamper with the data. Here's an example showing a better way for the backer to bake a loaf of bread, then wait while the bread supply is maxed out, then add the loaf to the inventory:

try {
    while (true) {
        Thread.sleep(4000);
        Bread bread = new Bread(id++);
        synchronized(Basket.breadList) {
            while (Basket.breadList.size() == 20) {
               Basket.breadList.wait();
            }
            Basket.breadList.add(bread);
            Basket.breadList.notifyAll();        
        }
    }
} catch (InterruptedException e) {
}

The wait method releases the lock, then re-acquires the lock before it can exit. Since this example holds the lock while it is checking and acting, once the inner while loop is exited from it is certain that the breadList contains less than 20 items. The customer should be rewritten similarly.

This version waits in a loop while holding the lock, checking the condition after emerging from the wait, because something may have changed while your thread didn't have the lock.

Also, just because your thread woke up doesn't mean you got a notification. The wait method can exit without having received a notification. See the Oracle tutorial for how to use wait and notify.

The only way for your thread to know what the size of the list really is is to check it while holding the lock, otherwise it could be changing on you (the other thread can be swapped in and change something in between your check and whatever action you take), resulting in the first thread basing its decisions on possibly stale information.

Likewise your customer shouldn't be removing something from the shared list without holding the lock on it. ArrayList is not threadsafe, also you don't want the state to change in between removing an item and sending the notification. If you are removing something from the list and then want to perform a notification, acquire the lock, then do the removal and notify together while holding the lock.

Don't hold a lock while sleeping, it's pointless and bad for performance. It would be better in this example, if you want to simulate needing time to create bread, for the sleep to come before the call to the Bread constructor.

The way your code swallows InterruptedException doesn't help your thread actually exit cleanly once interrupted. If you catch the InterruptedException outside the while (true) loop then the thread will respond to interruption by actually quitting its work and terminating.

The baker and customer should not be in charge of locking, it's confusing and makes it harder to understand how multithreading is applicable to real-life situations. Use a queue here, making the baker a producer and the customer a consumer. You have a shared data structure already, the arrayList, but you chose a data structure that isn't threadsafe and can't do blocking, the shared data structure needs to be in charge of protecting its own integrity. That way the roles are much clearer, with the locking, waiting, and notifying taking place in the shared data structure and not in the threads. Using a blocking queue from the java.util.concurrent package would be a good choice here, or write your own if you want the experience, it should be do-able once you read the linked tutorial. Once you use a separate queue the run method for the baker becomes:

public void run() {
    try {
        int id = 0;
        while (true) {
            Thread.sleep(4000);
            queue.put(new Bread(id++));
        }
    } catch (InterruptedException e) {
    }
}

while the queue's put method would be

public synchronized void put(Bread b) throws InterruptedException {
    while (breadList.size() == 20) {
        wait();
    }
    breadList.add(b);
    notifyAll();
}

assuming that breadList is a private instance member of the Queue.

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