有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

(问题已解决,解决方案如下)
我有两门课:装备课和指挥课。该设备是一种运行命令的设备,但我需要它能够同时运行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的执行停止了,它也没有完成它应该做的事情。 这种方法正确吗?我遗漏了什么,还是完全错了,我应该实现一个监控类来操作并发线程

编辑:多亏了@Gray,我用另一种完全不同的方法解决了这个问题,使用了Executors

这是最后一段代码,也许有一天会对某人有所帮助:

装备等级:

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()?。是的,在这种情况下,打电话是很有用的。运行()而不是。开始()


共 (2) 个答案

  1. # 1 楼答案

    ExecutorService是一个不错的选择。但是如果你想自己做,或者需要做一些更有趣的事情,我提供以下建议

    据我所知,这一切都是由Equip的queueCommand驱动的,它可以在任何时间、任何地点从任何线程调用。首先,Equip中的两个方法应该同步,这样commandQueue就不会被破坏。(您可以使用ConcurrentLinkedQueue,但要小心计数。)更好的是,将每个方法中的代码放在一个由queueCommand同步的块中

    但更进一步,我认为你们的两门课结合起来效果更好。将命令切换为简单的可运行命令,我会尝试以下方法:

    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上的同步,而是在运行之前先走。)

  2. # 2 楼答案

    如果从多个线程调用Command.run(),代码中存在大量的竞争条件。除非这是一个必须自己实现代码的家庭作业问题,否则我强烈建议使用1.6中添加的Java Executors之一。在本例中,Executors.newSingleThreadExecutor()是将正在运行的后台任务数限制为1所需的。这将允许无限数量的任务提交给ExecutorService,但在任何时候都只执行其中一个任务

    如果您需要在另一个任务已经在运行时将任务提交到block的线程,那么您可以使用以下方法。这将设置一个最多1个线程的池,并使用一个SynchronousQueue阻塞,直到工作线程消耗作业:

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

    但是如果是这样的话,那么您可以直接在synchronized块内调用任务,而不需要ExecutorService

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