2012-03-08 179 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上的同步,但在運行之前先行走。)

相關問題