简体   繁体   English

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

[英]Controlling thread using wait() and notify()

(Problem solved, solution below) (问题解决了,解决方案如下)
I have 2 classes: Equip and Command. 我有2个班:装备和指挥。 The equip is an equipment that run commands, but I need it to be able to run only 1 command at the same time. 装备是运行命令的设备,但我需要它能够同时运行1个命令。 A command is a thread, that executes on the run() function, while Equip is a normal class that don't extend anything. 命令是一个在run()函数上执行的线程,而Equip是一个不扩展任何东西的普通类。 Currently I have the following setup to run the commands: 目前我有以下设置来运行命令:

Command class: 命令类:

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

Equip class: 装备类:

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

However, this is not working. 但是,这不起作用。 Basically, the notify() isn't waking the command thread, so it'll never execute. 基本上,notify()不会唤醒命令线程,因此它永远不会执行。 I searched about the wait and notify protocol, but I couldn't find anything wrong with the code. 我搜索了等待和通知协议,但我发现代码没有任何问题。 I also tried calling the wait() directly from the queueCommand() method, but then the execution of the queueCommand stopped, and it also didn't do what it was supposed to do. 我也尝试直接从queueCommand()方法调用wait(),但随后queueCommand的执行停止了,它也没有按照它应该做的那样做。 Is this approach correct and I'm missing something or this is completely wrong and I should implement a Monitor class to manipulate the concurrent threads? 这种方法是否正确,我错过了什么或者这是完全错误的,我应该实现一个Monitor类来操作并发线程?

EDIT: I solved the problem using another completely different approach, using Executors, thanks to @Gray. 编辑:我使用另一个完全不同的方法解决了问题,使用Executors,感谢@Gray。

Here's the final code, it might help someone someday: 这是最终的代码,有一天它可能对某人有所帮助:

Equip class: 装备类:

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

In the Command class I just have a method to encapsulate the equip's execute method. 在Command类中,我只有一个方法来封装equip的execute方法。 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's executing on the same thread. 当我同时需要命令的结果时使用布尔waitCompletion,而不是调用新线程来执行它,我只是执行并等待,假装它在同一个线程上执行。 This question contains a good discussion on this matter: When would you call java's thread.run() instead of thread.start()? 这个问题包含了对这个问题的一个很好的讨论: 你何时会调用java的thread.run()而不是thread.start()? . And yes, this is a case where it's useful to call .run() instead of .start(). 是的,这是一个调用.run()而不是.start()的情况。

There are a large number of race conditions that exist in your code if Command.run() is called from multiple threads. 如果从多个线程调用Command.run()则代码中存在大量竞争条件。 Unless this is some sort of homework question where you have to implement the code yourself, I would highly recommend using one of the Java Executors which were added in 1.6. 除非这是你必须自己实现代码的某种功课问题,否则我强烈建议使用1.6中添加的Java Executors In this case the Executors.newSingleThreadExecutor() is what you need to limit the number of running background tasks to 1. This will allow an unlimited number of tasks to be submitted to the ExecutorService , but only one of those tasks will be executing at any one time. 在这种情况下, Executors.newSingleThreadExecutor()是您将运行后台任务的数量限制为1所需的。这将允许将无限数量的任务提交给ExecutorService ,但只有其中一个任务将在任何任务中执行一度。

If you need the thread that is submitting the tasks to block when another task is already running then you would use something like the following. 如果您需要在另一个任务已经运行时提交要阻止的任务的线程,那么您将使用类似下面的内容。 This sets up a pool of a maximum of 1 thread and uses a SynchronousQueue which blocks until the worker thread consumes the job: 这将设置一个最多1个线程的池,并使用一个SynchronousQueue阻塞,直到工作线程消耗该作业:

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

But if that was the case then you would just call the task directly inside of a synchronized block and you wouldn't need the ExecutorService . 但如果是这种情况,那么你只需要在synchronized块中直接调用任务,就不需要ExecutorService

Lastly, for any new concurrency programmer (of any language) I would recommend that you take the time to read some documentation on the subject. 最后,对于任何新的并发程序员(任何语言),我建议您花时间阅读有关该主题的一些文档。 Until you start recognizing the concurrent pitfalls inherent in threading even the simplest set of classes, it will be a frustrating process to get your code to work. 在您开始识别线程中固有的并发陷阱(即使是最简单的类)之前,让代码工作将是一个令人沮丧的过程。 Doug Lea's book is one of the bible's on the subject. Doug Lea的书是关于这一主题的圣经之一。 My apologies if I have underestimated your experience in this area. 如果我低估了你在这方面的经验,我表示歉意。

I think you should not have "synchronized" on the esperar method. 我认为你不应该在esperar方法上“同步”。 That will block using the object instances as the locking object. 这将阻止使用对象实例作为锁定对象。 Any other thread that attempts to wait will block AT ENTRY TO THE METHOD, not on the wait. 试图等待的任何其他线程将阻止AT进入方法,而不是等待。 So, the notifyAll will release the one thread that got into the method first. 因此,notifyAll将首先释放进入该方法的一个线程。 Of the remaining callers, only one will proceed with a call to esperar, which will then block on the wait(). 在其余的呼叫者中,只有一个将继续调用esperar,然后将在wait()上阻塞。 Rinse and repeat. 冲洗并重复。

ExectutorService is the way to go. ExectutorService是要走的路。 But if you want to do-it-yourself, or need to do something fancier, I offer the following. 但是如果你想自己动手,或者需要做一些更好的事情,我会提供以下内容。

I gather than this whole thing is driven by Equip's queueCommand, which might be callled from any thread anywhere at any time. 我收集的比这整个事情是由Equip的queueCommand驱动的,它可以随时随地从任何线程调用。 For starters, the two methods in Equip should by synchronized so commandQueue does not get trashed. 对于初学者来说,装备中的两个方法应该是同步的,因此commandQueue不会被破坏。 (You might use ConcurrentLinkedQueue, but be careful with your counts.) Better still, put the code in each method in a block synchronized by queueCommand. (您可以使用ConcurrentLinkedQueue,但要小心计数。)更好的是,将每个方法中的代码放在由queueCommand同步的块中。

But further, I think your two classes work better combined. 但进一步说,我认为你的两个班级组合得更好。 Switching Command to a simple Runnable, I'd try something like this: 切换命令到一个简单的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(); }
    }
}

You'll need to catch some exceptions, and figure out how to start things up and shut them down, but this should give an idea of how the wait and notify work. 您需要捕获一些异常,并弄清楚如何启动并关闭它们,但这应该可以了解等待和通知的工作方式。 (I'd look for some way to know when "run" was not waiting so I could skip synching on queueLock in queueCommand, but walk before you run.) (我想知道什么时候“run”没有等待,所以我可以在queueCommand中跳过同步queueLock,但是在你运行之前走路。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM