0

(问题已解决,解决方案如下)
我有 2 个课程:装备和指挥。装备是运行命令的设备,但我需要它能够同时运行一个命令。命令是一个线程,它在 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 类来操作并发线程?

编辑:感谢@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 类中,我只有一个方法来封装设备的执行方法。当我同时需要命令的结果时使用布尔值 waitCompletion,而不是调用一个新线程来执行它,我只是执行并等待,假装它在同一个线程上执行。这个问题包含一个关于这个问题的很好的讨论:你什么时候调用java的thread.run()而不是thread.start()?. 是的,在这种情况下,调用 .run() 而不是 .start() 很有用。

4

3 回答 3

2

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

如果您需要正在提交任务的线程在另一个任务已经运行时阻塞,那么您将使用类似以下的内容。这会设置一个最多包含 1 个线程的池,并使用SynchronousQueuewhich 阻塞,直到工作线程使用该作业:

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

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

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

于 2012-03-08T20:03:39.090 回答
0

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

于 2012-03-08T20:00:54.280 回答
0

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

我认为整个事情是由 Equip 的 queueCommand 驱动的,它可以随时随地从任何线程调用。对于初学者来说,Equip 中的两个方法应该是同步的,这样 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(); }
    }
}

您需要捕获一些异常,并弄清楚如何启动和关闭它们,但这应该可以让您了解等待和通知是如何工作的。(我会寻找一些方法来知道“运行”何时没有等待,这样我就可以跳过 queueCommand 中 queueLock 的同步,但在跑步之前先走。)

于 2012-03-08T21:43:23.677 回答