java併發編程(六)任務執行

大多數併發應用都是圍繞任務執行來構造的,任務通常是一些抽象且離散的工作單元

在線程中執行任務

在線程中執行任務大致分爲兩種情況。一種是單線程串行化執行任務,另一種是爲每一個任務創建線程來執行。

串行化執行任務
在單線程中串行的執行各項任務是執行任務最簡單的策略。
串行化執行任務的缺點是無法提供高吞吐率以及快速響應。
但是串行化執行任務的優點是更加的簡單和安全。
所以當任務比較少且執行時間很長,簡單說就是不需要高吞吐率和快速相應時使用串行處理機制是一種很好的選擇。

顯示的爲任務創建線程

通過爲每一個任務創建線程的方式執行任務,可以提供更高的響應性和吞吐率。
但是這種方式的缺點也很明顯。

  1. 線程生命週期的開銷非常大,線程的創建和銷燬都是有不小的開銷的。如果大量的輕量級請求使用這種方式,這些額外開銷是很大的,典型的像聊天軟件如果使用這種方式,額外開銷是很嚇人的。
  2. 資源消耗活躍的線程會消耗資源,尤其是內存。但是閒置的線程也會佔用很多內存,給垃圾回收器帶來壓力。而且大量線程競爭cpu資源也會消耗資源。如果項目中已經有足夠多的線程使cpu保持忙碌,那麼創建更多的線程反而會降低性能。
  3. 穩定性 jvm的參數,線程請求棧的大小等等因素都會限制可創建線程的數量,當這個數量超出後可能會拋出OutOfMemoryException異常。

綜合來說這種方式只能在一定範圍內提高吞吐率和響應性,超出這個範圍只會降低系統性能。

線程池執行任務
兩種方式都存在這一些嚴格的限制,所以日常開發中我們更多的會使用線程池來執行任務。

線程池中存在阻塞隊列也叫工作隊列,在隊列中保存所有待執行的任務。由線程池內創建的線程來執行這些任務。

跟前兩種方式相比,線程池通過會重用內部創建的線程,當線程執行完一個任務後會執行下一個任務,這種方式避免了頻繁創建和銷燬線程帶來的開銷,同時我們可以通過限制線程池的大小來保持處理器的忙碌狀態,防止大量線程競爭處理器資源。
ExecutorService和Executor的區別

  • 1.ExecutorService 接口繼承了Executor 接口,是Executor 的子接口。

  • 2.Executor接口中定義了execute()方法,用來接收一個Runnable接口的對象,而ExecutorService接口中定義的submit()方法可以接收Runnable和Callable接口對象。

  • 3.Executor接口中execute()方法不返回任何結果,而ExecutorService接口中submit()方法可以通過一個 Future 對象返回運算結果。

  • 4.Executor和ExecutorService除了允許客戶端提交一個任務,ExecutorService 還提供用來控制線程池的方法。比如:調用 shutDown() 方法終止線程池。

關於線程池會在後續的博客中做更詳細的講解。

callable和future

Callable是一種更好的執行任務的方式,它可以有返回值而且可以拋出異常。運行Callable任務可以拿到一個Future對象。

Future可以表示一個任務的生命週期,包括等待,執行,完成。
future.get方法的結果取決於future的狀態,完成之前是阻塞的,完成後會得到返回值或者拋出異常。
future.get還可以傳入時間參數,在該時間內如果任務沒有執行完成則會拋出TimeOutException異常,通過捕捉這個異常我們可以在任務執行時間過長時去處理這個任務。

注意

當兩個任務互相依賴時,如果一個任務執行很慢會拖累另一個任務的執行時間。最終導致性能沒有提高很多,代碼卻變得更復雜了。
只有當對大量相互獨立的任務進行併發處理時才能體現出併發處理任務帶來的真正的性能提升。
如果迫不得已要對一個執行時間很長的任務進行拆分的話一定要注意儘量保證各子任務之間的相互獨立。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章