2015-09-13 3 views
2

Я делаю сценарий шеф-повара, хлеба и клиента в Java с потоком. Так что в основном шеф-повар делает хлеб, клиент ест его, шеф-повар делает больше. Максимум - 20. Шеф-повар прекращает готовить хлеб, когда им 20. Клиент перестает есть, когда его нет. Но каждый раз, когда я использую notifyall, он ждет четыре секунды, прежде чем клиент съест его (предположительно, чтобы сделать еще 3 хлеба). Вот код для запуска в классе Chef (реализует Runnable)wait and notifyall ждет другой темы

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


    } 
} 

Вот код для Клиента:

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

     } 



    } 

} 

Вот код в контроллере:

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 - это просто архарист из хлеба.

помогите пожалуйста. Очень признателен!

+0

Почему вы не используете параллельный пакет, добавленный в Java 5? 'BlockingQueue' был сделан для такого рода случаев использования производителем-потребителем – dkatzel

+0

Почему у вас есть спит? И почему вы проверяете общие данные (например, 'Basket.breadList.size') вне блоков' synchronized'? –

+0

@dkatzel: я предполагал, что это домашнее задание - отсюда мой смутный ответ. И я полностью поддерживаю обучение использованию 'synchronzed' перед переходом к параллельным библиотекам. Конечно, больше не нужно синхронизировать блоки в производстве, это должно быть чем-то очень необычным, чтобы гарантировать его использование вместо java.util.concurrent. –

ответ

0

Ваш код не блокируется эффективно, так что, хотя один поток работает, другой может вмешиваться в данные. Вот пример, показывающий, лучший способ для покровитель испечь буханку хлеба, а затем ждать, пока запас хлеба увеличившихся, а затем добавить булку в инвентаре:

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) { 
} 

Метод ожидания снимает блокировку, то повторно приобретает замок, прежде чем он сможет выйти. Поскольку этот пример содержит блокировку во время проверки и действия, как только внутренний цикл while выходит из него, он уверен, что в файле breadList содержится менее 20 элементов. Заказчик должен быть переписан аналогичным образом.

Эта версия ждет цикла, удерживая замок, проверяя состояние после того, как выйдет из ожидания, потому что что-то могло измениться, в то время как у вашего потока не было блокировки.

Кроме того, только потому, что ваша нить проснулась, не означает, что вы получили уведомление. Метод wait может выйти без получения уведомления. См. the Oracle tutorial, как использовать wait и notify.

Единственный способ для вашего потока узнать, какой размер списка на самом деле - проверить его, удерживая замок, иначе он может меняться (другой поток можно поменять местами и изменить что-то между вашим проверка и любые действия, которые вы принимаете), в результате чего первый поток основывается на своих решениях относительно, возможно, устаревшей информации.

Кроме того, ваш клиент не должен удалять что-либо из общего списка, не удерживая на нем замок. ArrayList не является потокобезопасным, также вы не хотите, чтобы состояние изменялось между удалением элемента и отправкой уведомления. Если вы удаляете что-то из списка, а затем хотите выполнить уведомление, приобретите блокировку, затем выполните удаление и сообщите вместе, удерживая блокировку.

Не держите замок во сне, это бессмысленно и плохо для исполнения. Было бы лучше в этом примере, если вы хотите имитировать необходимость времени на создание хлеба, чтобы сон появился перед вызовом конструктора Хлеба.

Способ, которым ваш код проглатывает InterruptedException, не помогает вашему потоку фактически выйти из строя после прерывания. Если вы поймаете InterruptedException вне цикла while (true), поток будет реагировать на прерывание, фактически прекратив работу и завершая работу.

Пекарь и клиент не должны отвечать за блокировку, это запутывает и затрудняет понимание того, как многопоточность применима к реальным ситуациям. Используйте очередь здесь, делая пекаря производителем и клиентом потребителем. У вас уже есть общая структура данных, arrayList, но вы выбрали структуру данных, которая не является потокобезопасной и не может блокировать, общая структура данных должна отвечать за защиту своей собственной целостности. Таким образом, роли намного яснее, причем блокировка, ожидание и уведомление происходят в общей структуре данных, а не в потоках. Использование блокирующей очереди из пакета java.util.concurrent было бы хорошим выбором здесь или написать свой собственный, если вы хотите получить опыт, он должен быть работоспособным, как только вы прочтете связанный учебник. После того, как вы используете отдельную очередь метод запуска для пекаря становится:

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

в то время как метод поставить в очереди было бы

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

при условии, что breadList является частным членом экземпляра очереди.

+0

Спасибо. Позднее я решил просто избавиться от времени ожидания после notifyAll(). – Meinkraft

1

После того, как ваш клиент ест какой-либо хлеб, он всегда ждет 4 секунды. Нет ничего, чтобы предотвратить это ожидание (обычно должно быть if()).

Правило: никогда не спите() безоговорочно, если вы не уверены, что так оно и должно быть. Вы всегда спите(), потому что больше нечего делать и, вероятно, не будет на какое-то время. Поэтому вам нужно проверить, есть ли.

Кроме того, notifyAll() обычно выполняется немедленно после Некоторое предварительное действие, которое делает вещи доступными для обработки других потоков.
Правило: вызывать notifyAll() в контейнере сразу же после того, как вы что-то в нем помещаете.

Также неясно, в каком коде и в какое время шеф-повар добавляет Хлеб в корзину. Я полагаю, что хлеб добавляет себя в свой собственный конструктор - если это так, это анти-шаблон. Держите хлеб простым и здоровым, он будет лучше выглядеть таким образом. Сделать шеф-повар сделать работу. Я бы волновался, если, в то время как замешивание и выпечка хлеба влезет в корзину.

В общем, попробуйте написать свой код точно так же, как действуют актеры в реальном мире. Будет ли шеф-повар уведомлять клиентскую корзину не пустым? Когда? Будет ли клиент уведомлять шеф-повара, когда корзина пуста? Когда? Когда кто-нибудь из них ждет?

+1

Все хорошие моменты! Я бы добавил: переместите два условия 'if', которые запускают одну строку перед секцией' synchronize' - внутри 'synchronize'. – alfasin

Смежные вопросы