(问题解决了,解决方案如下)
我有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()的情况。
答案 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,但是在你运行之前走路。)