2012-03-08 76 views
0

(问题已解决,解决方案如下)
我有2个类:装备和命令。装备是一个运行命令的设备,但我需要它能够同时运行一个命令。 命令是一个线程,它在run()函数上执行,而Equip是一个不扩展任何东西的普通类。 目前,我有以下设置来运行命令:控制线程使用wait()和notify()

Command类:

@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()不会唤醒命令线程,所以它永远不会执行。 我搜索了wait和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) { 
     } 
    } 
} 

在命令类我只需要封装装备的执行方法的方法。 当我需要命令的结果的同时使用布尔型的waitCompletion,而不是调用一个新线程来执行它,我只是执行并等待,假装它在同一个线程上执行。这个问题包含了关于这个问题的一个很好的讨论:When would you call java's thread.run() instead of thread.start()?。是的,这是一个调用.run()而不是.start()的情况。

+0

我们在这里讨论的每种线程有多少种?在这段代码中至少有一些不好的竞争条件,可能会导致意想不到的行为,如果每种类型中有超过1个,就会看到这种行为。 – Gray 2012-03-08 19:59:18

+0

'commandQueue'是一个TreeSet吗?它是同步的吗? – Gray 2012-03-08 20:02:03

+0

Equip类没有任何子类,但有几种类型的Command。我不认为这是一个问题,通常也只有1或2个命令同时执行。我只是这样做的,因为如果2个命令在同一时间执行,事情可能会出错,他们将无法正确执行(由于我的解决方案的复杂性,设备实际上是一个通过运行C代码的UDP控制的机器)。 – 2012-03-08 20:03:18

回答

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

但是,如果是这样的话,那么你就只需要直接调用任务的​​内块,你不需要ExecutorService。最后,对于任何新的并发编程人员(任何语言),我建议您花些时间阅读关于该主题的一些文档。在你开始认识到线程中固有的并发陷阱之前,即使是最简单的类也是如此,否则让你的代码运行起来将是一个令人沮丧的过程。 Doug Lea's book是关于这个问题的圣经之一。如果我低估了你在这方面的经验,我很抱歉。

+0

我会尽力,谢谢。 – 2012-03-08 20:07:03

+0

我正在改变''newSingleThreadExecutor()'方法,但我有一些需要执行的命令,并在相同的方法上得到结果。为此,我调用'command.run()'而不是'command.start()'。为了复制这种行为,将'executor.execute(command);'放入同步块中就足够了? – 2012-03-08 20:15:10

+0

是的。如果你有一个创建作业的单线程,那么你就不需要'synchronized'块。这只会对多个提交者调用'equipment.run()'有必要。 – Gray 2012-03-08 20:18:29

0

我认为你不应该在esperar方法上“同步”。这将阻止将对象实例用作锁定对象。任何其他尝试等待的线程都会阻止AT进入方法,而不是等待。所以,notifyAll将首先释放进入该方法的一个线程。其余的来电者中,只有一个人会打电话给esperar,然后阻止等待()。冲洗并重复。

+0

'wait()'释放锁定(如果没有,使用wait/notify将不可能) – Mat 2012-03-08 20:04:36

+0

我这样做了,但后来发现错误,说我没有锁定对象监视器。 – 2012-03-08 20:05:40

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

相关问题