2012-03-01 58 views
15

我環顧四周,但還沒有找到答案,所以我想確認一下。Java線程池/執行程序服務和wait()s - 線程和任務隊列發生了什麼?

說我有一個固定大小的線程池 - ExecutorService pool = Executors.newFixedThreadPool(5);

而且我有一些代碼:

pool.execute(new Runnable(){ 
    try{ 
     Object waitForMe = doSomethingAndGetObjectToWaitFor(); 
     waitForMe.wait(); 
     doSomethingElse(); 
    }catch(Exception e){ throw new RunTimeException(e) } 

}); 

讓我們假設上面的代碼被稱爲幾百倍。池中只有5個線程(所以上面的語句中只有5個應該在一個點上)。另外假設wait()位於一個對象上,該對象正在對第三方進行一些I/O調用,並在操作完成時等待回調,因此自然需要一段時間才能完成。

現在我的問題是,當其中一個任務達到wait()時的行爲是什麼,任務是否進入休眠狀態,然後線程池中的線程將另一個任務從隊列中取出並開始運行?

如果正在等待的任務進入睡眠狀態,當它獲得notify()並且醒來時會發生什麼?線程是否回到線程池的隊列中(在前面或後面),並等待5個線程中的一個線程能夠繼續執行它(即調用doSomethingelse())?或者執行它的線程也進入休眠狀態,即5個執行程序線程中的一個線程等待任務(這是我所假設的)?或者執行程序線程接受另一個任務,並在第一個任務從wait()返回時被簡單地中斷?

回答

16

wait()是阻塞操作:

造成當前線程等待,直到其他線程調用notify()方法或notifyAll()

這意味着該線程池中將等待,但從外面看,目前的任務需要很長時間才能完成。這也意味着如果執行5個任務並且它們全部爲wait(),則Executor不能處理剩餘的任務,其中,等待隊列中的等待

確實,執行程序線程本身進入休眠狀態,允許其他線程切換並使用CPU(因此可以有數百個線程同時等待,並且系統仍然可以響應),但仍然線程「不可用」,並且受阻。

另一個有趣的功能是中斷 - 如果線程等待或睡覺,你可以打斷它。請注意,wait()Thread.sleep()均聲明InterruptedException。通過ExecutorService,您只需撥打以下電話就可以充分利用此優勢:future.cancel()future是將任務提交給ExecutorService時獲得回報的對象)。

最後我認爲你應該重新設計你的解決方案。取而代之的積極等待外部系統完成,提供了一個API與回調:

pool.execute(new Runnable(){ 
    try{ 
     doSomethingAndCallMeBackWhenItsDone(new Callback() { 
      public void done() { 
       doSomethingElse(); 
      } 
     }); 
    }catch(Exception e){ throw new RunTimeException(e) } 

}); 

這樣的外部系統的API將簡單地通知您結果已經準備就緒,你將不必等待,阻止ExecutorService 。最後,如果doSomethingElse()需要花費大量的時間,你甚至可能決定安排它和,而不是使用外部第三方I/O線:

pool.execute(new Runnable(){ 
    try{ 
     doSomethingAndCallMeBackWhenItIsDone(new Callback() { 
      public void done() { 
       pool.submit(new Callbale<Void>() { 
        public Void call() { 
         doSomethingElse(); 
        } 
       } 
      } 
     }); 
    }catch(Exception e){ throw new RunTimeException(e) } 

}); 

更新:你問該怎麼辦超時?這裏是我的想法:

pool.execute(new Runnable(){ 
    try{ 
     doSomethingAndCallMeBackWhenItsDone(new Callback() { 
      public void done() { 
       doSomethingElse(); 
      } 
      public void timeout() { 
       //opps! 
      } 
     }); 
    }catch(Exception e){ throw new RunTimeException(e) } 

}); 

我想你可以在第三方側實現超時,如果發生有超時,只需調用timeout()方法。

+1

感謝您的好評!通過回調選項,設置超時的最佳方式是什麼(比如說,如果沒有任何錯誤發生,則希望等待回調x秒)。我能想到的唯一方法是記下當前系統毫秒數並將其保存在一個列表中,並讓另一個線程監視超出當前時間並觸發錯誤的呼叫列表。我需要找到一本關於併發,回調等的好書。再次感謝! – NightWolf 2012-03-01 11:02:14

+1

@NightWolf:關於超時查看我更新的答案。當談到一本好書時,[Java併發實踐](http://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601)是必須的。 – 2012-03-01 11:06:05

+0

感謝您的更新和圖書鏈接,好主意。可悲的是我對第三方沒有任何控制權。 – NightWolf 2012-03-01 11:12:04

1

wait()無法知道任何有關胎面花紋。線程池無法知道wait()的任何內容。所以他們不能相互作用。

他們像往常一樣工作 - wait()只是一個長時間運行的阻塞操作,線程池只是一個運行在有限的線程池上的runnable隊列。

0

我會評論托馬斯的回答,但我的聲望不允許(但),對不起。

我知道這個問題已經過時了,但對於最終還是讀完這個頁面的人來說,看看Future,特別是番石榴的ListenableFuture,它可以讓你註冊回調和連鎖未來,你的線程(並且因此將線程釋放回池中以用於其他用途)。

0

所有5個線程將​​被阻止,應用程序將處於非生產狀態。

添加到Tomasz答案,我想實現超時機制如下。

  Future<Long> futureResult = service.execute(myCallable); 
      Long result = null; 
      try{ 
       result = futureResult.get(5000, TimeUnit.MILLISECONDS); 
      }catch(TimeoutException e){ 
       System.out.println("Time out after 5 seconds"); 
       futureResult.cancel(true); 
      }catch(InterruptedException ie){ 
       System.out.println("Error: Interrupted"); 
      }catch(ExecutionException ee){ 
       System.out.println("Error: Execution interrupted"); 
      } 

除了TimeoutException,您可以在InterruptedException & ExecutionException取消未來。如果使用submit()而不是execute(),則框架本身會吞噬InterruptedException & ExecutionException