Управление потоком с помощью wait () и notify () - PullRequest
0 голосов
/ 08 марта 2012

(проблема решена, решение приведено ниже)
У меня 2 класса: экипировка и командование. Оборудование - это оборудование, которое запускает команды, но мне нужно, чтобы оно могло выполнять только 1 команду одновременно. Команда - это поток, который выполняется в функции run (), а Equip - это обычный класс, который ничего не расширяет. В настоящее время у меня есть следующие настройки для запуска команд:

Командный класс:

@Override
public void run() {
    boolean execute = equip.queueCommand(this);
    if (!execute) {
        // if this command is the only one on the queue, execute it, or wait.
        esperar();
    }
    // executes the command.....
    equip.executeNextCommand();
}


synchronized public void esperar() {
    try {
        this.wait();
    } catch (Exception ex) {
        Log.logErro(ex);
    }
}

synchronized public void continue() {
    this.notifyAll();
}

Класс снаряжения:

public boolean queueCommand(Command cmd) {
    // commandQueue is a LinkedList
    commandQueue.addLast(cmd);
    return (commandQueue.size() == 1);
}

public void executeNextCommand() {
    if (commandQueue.size() >= 1) {
        Command cmd = commandQueue.pollFirst();
        cmd.continue();
    }
}

Однако это не работает. По сути, notify () не пробуждает поток команд, поэтому он никогда не будет выполнен. Я искал протокол ожидания и уведомления, но не смог найти ничего плохого в коде. Я также попытался вызвать wait () напрямую из метода queueCommand (), но затем выполнение queueCommand прекратилось, и он также не сделал то, что должен был сделать. Является ли этот подход правильным, и я что-то упускаю, или это совершенно неправильно, и я должен реализовать класс Monitor для управления параллельными потоками?

РЕДАКТИРОВАТЬ: Я решил проблему, используя другой совершенно другой подход, используя Executors, благодаря @ Grey.

Вот окончательный код, он может кому-нибудь когда-нибудь помочь:

Класс экипировки:

private ExecutorCompletionService commandQueue = new ExecutorCompletionService(Executors.newFixedThreadPool(1));

public void executeCommand(Command cmd, boolean waitCompletion) {
    commandQueue.submit(cmd, null);
    if (waitCompletion) {
        try {
            commandQueue.take();
        } catch (Exception ex) {
        }
    }
}

В классе Command у меня просто есть метод для инкапсуляции метода execute из equip. Логическое значение waitCompletion используется, когда мне одновременно нужен результат команды, и вместо вызова нового потока для его выполнения я просто выполняю и жду, делая вид, что выполняется в том же потоке. Этот вопрос содержит хорошее обсуждение по этому вопросу: Когда бы вы вызвали java's thread.run () вместо thread.start ()? . И да, это тот случай, когда полезно вызывать .run () вместо .start ().

Ответы [ 3 ]

2 голосов
/ 09 марта 2012

В вашем коде существует большое количество состояний гонки, если Command.run() вызывается из нескольких потоков. Если это не какой-то домашний вопрос, когда вам нужно реализовать код самостоятельно, я настоятельно рекомендую использовать один из Java Executors, который был добавлен в 1.6. В этом случае Executors.newSingleThreadExecutor() - это то, что вам нужно, чтобы ограничить количество запущенных фоновых задач до 1. Это позволит неограниченному количеству задач быть отправленным на ExecutorService, но только одна из этих задач будет выполняться на любом один раз.

Если вам нужен поток, который отправляет задачи в блок , когда другая задача уже запущена, вы должны использовать что-то вроде следующего. Это устанавливает пул максимум из 1 потока и использует SynchronousQueue, который блокирует, пока рабочий поток не использует задание:

final ExecutorService executorServer =
    new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
         new SynchronousQueue<Runnable>());

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

Наконец, для любого нового программиста по параллелизму (любого языка) я бы порекомендовал вам уделить время чтению некоторой документации по этому вопросу. Пока вы не начнете распознавать параллельные ловушки, присущие многопоточности даже простейшего набора классов, заставить ваш код работать работать будет сложно. Книга Дуга Ли - одна из библий по этому вопросу. Приношу свои извинения, если я недооценил ваш опыт в этой области.

0 голосов
/ 09 марта 2012

ExectutorService - это путь.Но если вы хотите сделать это самостоятельно или вам нужно сделать что-то более необычное, я предлагаю следующее.

Я понимаю, что все это обусловлено queueCommand от Equip, которая может вызываться из любого потока в любом месте вв любой момент.Для начала, два метода в Equip должны быть синхронизированы, так что commandQueue не будет сброшен.(Вы можете использовать ConcurrentLinkedQueue, но будьте осторожны с вашими подсчетами.) Еще лучше, поместите код каждого метода в блок, синхронизированный посредством queueCommand.

Но, кроме того, я думаю, что ваши два класса работают лучше вместе.Переключив Command на простой Runnable, я бы попробовал что-то вроде этого:

class Equip  {
    private Object  queueLock = new Object();  // Better than "this". 
    private LinkedList<Runnable>  commandQueue = new LinkedList<Runnable>();

    private void run() {
        for (;;)  {
            Runnable  cmd = equip.getNextCommand();
        if (cmd == null)  {
                // Nothing to do.
                synchronized (queueLock)  { queueLock.wait(); }
            }
            else
                cmd.run();
        }
    }
    // Adds commands to run.
    public boolean queueCommand( Runnable cmd )  {
        synchronized (queueCommand)  { commandQueue.addLast( cmd ); }
        synchronized (queueLock)  {
            // Lets "run" know queue has something in it if it
            // is in a wait state.
            queueLock.notifyAll();
        }
    }
    private Runnable getNextCommand()  {
        synchronized (queueCommand)  { return commandQueue.pollFirst(); }
    }
}

Вам нужно будет перехватить некоторые исключения и выяснить, как запускать и выключать их, но это должно датьпредставление о том, как ждать и уведомлять работу.(Я бы искал способ узнать, когда «run» не ожидал, поэтому я мог пропустить синхронизацию по queueLock в queueCommand, но пройти до того, как вы запустите.)

0 голосов
/ 09 марта 2012

Я думаю, вы не должны были "синхронизироваться" на методе esperar.Это заблокирует использование экземпляров объекта в качестве объекта блокировки.Любой другой поток, который пытается ждать, будет блокироваться при входе в метод, а не при ожидании.Таким образом, notifyAll освободит один поток, который попал в метод первым.Из оставшихся вызывающих абонентов только один продолжит вызов esperar, который затем заблокирует функцию wait ().Промыть и повторить.

...