首页 文章

使用wait()和notify()控制线程

提问于
浏览
0

(Problem solved, solution below)
我有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()不会唤醒命令线程,因此它永远不会执行 . 我搜索了等待和通知协议,但我发现代码没有任何问题 . 我也尝试直接从queueCommand()方法调用wait(),但随后queueCommand的执行停止了,它也没有按照它应该做的那样做 . 这种方法是否正确,我错过了什么或者这是完全错误的,我应该实现一个Monitor类来操作并发线程?

编辑:我使用另一个完全不同的方法解决了问题,使用Executors,感谢@Gray .

这是最终的代码,有一天它可能对某人有所帮助:

装备类:

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类中,我只有一个方法来封装在同一个线程上执行的装备's execute method. The boolean waitCompletion is used when I need the result of the command at the same time, and instead of calling a new thread to execute it, I just execute and wait, pretending that it' . 这个问题包含了对此事的良好讨论:When would you call java's thread.run() instead of thread.start()? . 是的,这是一个调用.run()而不是.start()的情况 .

3 回答

  • 2

    如果从多个线程调用 Command.run() ,则代码中存在大量竞争条件 . 除非这是你必须自己实现代码的某种功课问题,否则我强烈建议使用1.6中添加的Java Executors . 在这种情况下, Executors.newSingleThreadExecutor() 是您将运行后台任务的数量限制为1所需的 . 这将允许将无限数量的任务提交到 ExecutorService ,但这些任务中只有一个将在任何时间执行 .

    如果您需要在另一个任务已经运行时提交要阻止的任务的线程,那么您将使用如下所示的内容 . 这将设置一个最多1个线程的池,并使用 SynchronousQueue 阻塞,直到工作线程消耗该作业:

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

    但如果是这种情况,那么你只需要在 synchronized 块内直接调用任务,就不需要 ExecutorService .

    最后,对于任何新的并发程序员(任何语言),我建议您花时间阅读有关该主题的一些文档 . 在您开始识别线程中固有的并发陷阱(即使是最简单的类)之前,让代码工作将是一个令人沮丧的过程 . Doug Lea's book是关于这个主题的圣经之一 . 如果我低估了你在这方面的经验,我表示歉意 .

  • 0

    我认为你不应该在esperar方法上“同步” . 这将阻止使用对象实例作为锁定对象 . 试图等待的任何其他线程将阻止AT进入方法,而不是等待 . 因此,notifyAll将首先释放进入该方法的一个线程 . 在其余的呼叫者中,只有一个将继续调用esperar,然后将在wait()上阻塞 . 冲洗并重复 .

  • 0

    ExectutorService是要走的路 . 但是如果你想自己动手,或者需要做一些更高级的事情,我会提供以下内容 .

    我收集的比这整个事情是由Equip的queueCommand驱动的,它可以随时随地从任何线程调用 . 对于初学者来说,装备中的两个方法应该是同步的,因此commandQueue不会被破坏 . (您可以使用ConcurrentLinkedQueue,但要小心计数 . )更好的是,将每个方法中的代码放在由queueCommand同步的块中 .

    但进一步说,我认为你的两个 class 组合得更好 . 切换命令到一个简单的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”没有等待,所以我可以在queueCommand中跳过同步queueLock,但是在你运行之前走路 . )

相关问题