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

时间:2012-03-08 19:53:05

标签: java multithreading synchronization executor

(问题解决了,解决方案如下)
我有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类中,我只有一个方法来封装equip的execute方法。 当我同时需要命令的结果时使用布尔waitCompletion,而不是调用新线程来执行它,我只是执行并等待,假装它在同一个线程上执行。这个问题包含了对此问题的良好讨论:When would you call java's thread.run() instead of thread.start()?。是的,这是一个调用.run()而不是.start()的情况。

3 个答案:

答案 0 :(得分: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是关于这个主题的圣经之一。如果我低估了你在这方面的经验,我很抱歉。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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

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

但是,我认为你的两个课程的组合效果更好。切换命令到一个简单的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,但是在你运行之前走路。)