繁体   English   中英

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

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

(问题解决了,解决方案如下)
我有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,而不是调用新线程来执行它,我只是执行并等待,假装它在同一个线程上执行。 这个问题包含了对这个问题的一个很好的讨论: 你何时会调用java的thread.run()而不是thread.start()? 是的,这是一个调用.run()而不是.start()的情况。

如果从多个线程调用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的书是关于这一主题的圣经之一。 如果我低估了你在这方面的经验,我表示歉意。

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

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,但是在你运行之前走路。)

暂无
暂无

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

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