線程池的探索

1、線程池

併發的基礎是java.lang.Threads類。 Thread執行類型爲java.lang.Runnable的對象。html

直接使用Thread類有如下缺點:java

Ø  建立新線程會致使一些性能開銷。git

Ø  太多的線程可能致使性能降低,由於CPU須要在這些線程之間切換。github

Ø  不能輕易地控制線程數,所以線程過多會致使內存不足錯誤。web

與直接使用Threads相比,java.util.concurrent包提供了對併發性的改進支持。 算法

使用線程池的優勢:apache

Ø  下降資源消耗:經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。編程

Ø  提升響應速度:當任務到達時,任務能夠不須要等到線程建立就能當即執行。數組

Ø  提升線程的可管理性:線程是稀缺資源,若是無限制的建立。不只僅會下降系統的穩定性,使用線程池能夠統一分配,調優和監控。可是要作到合理的利用線程池。必須對於其實現原理了如指掌。緩存

2、Executor

Executor框架主要由三個部分組成:任務,任務的執行,異步計算的結果。

主要的類和接口簡介以下:

ExecutorService

真正的線程池接口。

ScheduledExecutorService

能和Timer/TimerTask相似,解決那些須要任務重複執行的問題。

ThreadPoolExecutor

ExecutorService的默認實現。

ScheduledThreadPoolExecutor

繼承ThreadPoolExecutorScheduledExecutorService接口實現,週期性任務調度的類實現。

Future

表明異步計算的結果

Runnable

能夠被ThreadPoolExecutor或其餘執行的主體邏輯代碼

 

java.util.concurrent 包包含多個 Executor 實現,每一個實現都實現不一樣的執行策略。


 

執行程序一般經過工廠方法例示,而不是經過構造函數。Executors 類包含用於構造許多不一樣類型的 Executor 實現的靜態工廠方法:

Ø  newSingleThreadExecutor()

建立一個單線程的線程池。這個線程池只有一個線程在工做,也就是至關於單線程串行執行全部任務。若是這個惟一的線程由於異常結束,那麼會有一個新的線程來替代它。此線程池保證全部任務的執行順序按照任務的提交順序執行。

Ø  newFixedThreadPool()

建立固定大小的線程池。每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結束,那麼線程池會補充一個新線程。

Ø  newCachedThreadPool()

建立一個可緩存的線程池。若是線程池的大小超過了處理任務所須要的線程,

那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增長時,此線程池又能夠智能的添加新線程來處理任務。此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系統(或者說JVM)可以建立的最大線程大小。

Ø  newScheduledThreadPool()

建立一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。

 

所需JAR包:

Jdk 1.5 + -> java.util.concurrent

 

參考資料:

FixedThreadPool, CachedThreadPool, or ForkJoinPool? Picking correct Java executors for background tasks - https://zeroturnaround.com/rebellabs/fixedthreadpool-cachedthreadpool-or-forkjoinpool-picking-correct-java-executors-for-background-tasks/

java自帶線程池和隊列詳細講解 - https://www.oschina.net/question/565065_86540

Java併發編程與技術內幕:線程池深刻理解 -http://blog.csdn.net/evankaka/article/details/51489322

 

3、Fork/Join

Fork/Join框架是Java 7提供了的一個用於並行執行任務的框架, 是一個把大任務分割成若干個小任務,最終彙總每一個小任務結果後獲得大任務結果的框架。

Fork-Join框架有本身的適用範圍。若是一個應用能被分解成多個子任務,而且組合多個子任務的結果就可以得到最終的答案,那麼這個應用就適合用 Fork-Join框架模式來解決。

Fork-Join框架可以解決不少種類的並行問題。軟件開發人員只須要關注任務的劃分和中間結果的組合就能充分利用並行平臺的優良性能。其餘和並行相關的諸多難於處理的問題,例如負載平衡、同步等,均可以由框架採用統一的方式解決。

Fork-Join框架是ExecutorService接口的一種具體實現,目的是爲了更好地利用多處理器。它是爲那些可以被遞歸地拆解成子任務的工做類型量身設計的。相似於ExecutorService接口的其餘實現,Fork-Join框架會將任務分發給線程池中的工做線程。

Fork-Join框架的核心是ForkJoinPool類,它是對AbstractExecutorService類的擴展。ForkJoinPool實現了工做竊取算法,並能夠執行ForkJoinTask任務。

工做竊取算法

Fork-Join框架經過一種稱做工做竊取(work stealing的技術減小了工做隊列的爭用狀況。每一個工做線程都有本身的工做隊列,這是使用雙端隊列(或者叫作 deque)來實現的(Java 6 在類庫中添加了幾種 deque 實現,包括 ArrayDeque LinkedBlockingDeque)。當一個任務劃分一個新線程時,它將本身推到 deque 的頭部。當一個任務執行與另外一個未完成任務的合併操做時,它會將另外一個任務推到隊列頭部並執行,而不會休眠以等待另外一任務完成(像 Thread.join() 的操做同樣)。當線程的任務隊列爲空,它將嘗試從另外一個線程的 deque 的尾部竊取另外一個任務。

所需JAR包:

Jdk 1.7 + -> java.util.concurrent

 

參考資料:

1.     方騰飛,聊聊併發(八)——Fork/Join框架介紹

2.     JDK 7 中的 Fork/Join 模式

3.     Java 理論與實踐: 應用 fork-join 框架

4、Google Guava[建議使用]

Guava工程包含了若干被Google Java項目普遍依賴的核心庫,例如:集合 [collections] 、緩存 [caching] 、原生類型支持 [primitives support] 、併發庫 [concurrency libraries] 、通用註解 [common annotations] 、字符串處理 [string processing] I/O 等等。全部這些工具天天都在被Google的工程師應用在產品服務中。

 

關於Java併發操做,guava提供了一組API用於封裝線程。

有兩個關鍵接口:

ListenableFuture:完成後觸發回調的Future

Service:抽象可開啓和關閉的服務,幫助你維護服務的狀態邏輯

其中:

1ListenableFuture接口繼承自JDK concurrent包下的Future接口,對比Future接口有一些優勢:

Ø  大多數Futures 方法中須要它。

Ø  轉到ListenableFuture 編程比較容易。

Ø  Guava提供的通用公共類封裝了公共的操做方方法,不須要提供FutureListenableFuture的擴展方法。

 

2Service接口用於封裝一個服務對象的運行狀態、包括startstop等方法。例如web服務器,RPC服務器、計時器等能夠實現這個接口。對此類服務的狀態管理並不輕鬆、須要對服務的開啓/關閉進行妥善管理、特別是在多線程環境下尤其複雜。Guava包提供了一些基礎類幫助你管理複雜的狀態轉換邏輯和同步細節。

實現:

AbstractIdleService

AbstractExecutionThreadService

AbstractScheduledService

AbstractService

ServiceManager

 

所需JAR包:

google-guava_xxx.jar

 

參考資料:

Google Guava官方教程(中文版) | 併發編程網

http://www.baeldung.com/thread-pool-java-and-guava

 

5、threadly

一個工具庫,以協助安全併發java開發。提供一個獨特的基於優先級的線程池,以及安全地分發線程工做的方法。

 

參考資料:

GitHub: https://github.com/threadly/threadly

APIhttp://threadly.github.io/threadly/javadocs/4.4.2/

Download: http://mvnrepository.com/artifact/org.threadly/threadly/4.9.0

Examples: https://github.com/threadly/threadly_examples/blob/master/src/main/java/org/threadly/examples/features/PrioritySchedulerExample.java

 

6、talent-thread-pool

talent-thread-pool是基於jdk5內置線程池的封裝,省卻你一些事件的框架
1
、幫你完成使用線程池所帶來的繁瑣的同步安全工做
2
、爲你提供一個更靠譜的RejectedExecutionHandlerjdk自帶的是拋異常,本框架默認的是用定時繼續提交) 
3
、爲你提供一個更友好的ThreadFactoryjdk自帶的Factory產生出來的Thread名字是形如thread-pool-1的,本框架默認的是形如:myname-1,其中「myname」是應用提供的參數) 
4
、提供更簡單的ThreadPoolExecutor構造器,固然你也能夠根據業務須要構造更細化的ThreadPoolExecutor 

 

 

 

 

參考資料:

http://tywo45.iteye.com/blog/1944341

http://tywo45.iteye.com/blog/1536159

7、Eclipse Jobs

Eclipse 提供了一套多線程類庫(包括 Job 等)極大的方便了開發人員對多線程程序的處理。本文經過對 Eclipse 內核代碼的研究,分析 Eclipse 多線程庫的內部實現機制,特別是其內部線程池的實現方式,Job 的調度,線程同步機制等。

 

Eclipse org.eclipse.core.runtime.osgi 運行時插件裏提供了 Jobs API Jobs API 被普遍的應用到 Eclipse 平臺中,用戶所開發的 eclipse 插件裏。 Job Eclipse 運行時重要的組成部分(基於 equinox OSGi 框架則是 Eclipse 運行時的最重要的組成部分)。 Job 能夠理解成被平臺調用異步運行的代碼塊。 Job 能夠異步的執行,多個 Jobs 能夠併發執行。那麼讀者會問了?爲何 eclipse 平臺要提供這樣的 API 出來,爲何不直接使用 java.lang.Thread 呢?

緣由有如下幾點:

1)性能更好:經過使用線程池實現了線程共享,減小了建立和銷燬線程的開銷,提升了性能。

2)交互響應信息:Job 提供了一個框架,開發人員使用 Job 很容易實現與用戶的交互,例如容許用戶取消 Job 的執行或者顯示 Job

3)調度的靈活性:能夠立刻運行一個 Job,能夠稍後運行一個 Job, 還能夠反覆的運行一個 Job

4Job 的監聽機制:Job 監聽器監聽 Job 的狀態信息,好比,知道一個 Job 什麼時候開始運行以及什麼時候結束運行等。

5)優先級及互斥的靈活應用:Job 提供了多種方式來控制 Job 的調度,開發者能夠設定 Job 的優先級(讀者應注意這一點,JobManager 不保證優先級高的 Job 必定比優先級低的 Job 先被調度執行),也可使用調度規則保證 Jobs 的同步與互斥。

6)使用Job能夠提升程序的性能,節省線程建立和銷燬的開銷。Eclipse中的Job封裝了線程池的實現。當咱們啓動一個Job時,Eclipse不會立刻新建一個Thread,它會在它的線程池中尋找是否有空閒的線程,若是有空閒線程,就會直接用空閒線程運行你的Job。一個Job終止時,它所對應的線程也不會當即終止,它會被返回到線程池中以備重複利用。這樣,咱們能夠節省建立和銷燬線程的開銷。

 

1. Jobs 框架

 

 

2. Work WorkPool,線程池機制

 

 

3. Schedule方法調用順序

 

所需JAR包:

org.eclipse.core.jobs-3.3.0.jar

org.eclipse.core.runtime-3.1.0.jar

org.eclipse.osgi-3.2.0-v20060601.jar

參考資料:

https://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-mthrd/

https://www.ibm.com/developerworks/cn/opensource/os-cn-eclipse-multithrd/

http://defrag-sly.iteye.com/blog/344837

8、Tomcat ThreadPool

Tomcat 的線程池位於tomcat-util.jar文件中,包含了兩種線程池方案。

方案一:使用APRPool技術,使用了JNI

方案二:使用Java實現的ThreadPool。這裏介紹的是第二種。若是想了解APRPool技術,能夠查看APR的源代碼。

ThreadPool默認建立了5個線程,保存在一個200維的線程數組中,建立時就啓動了這些線程,固然在沒有請求時,它們都處理「等待」狀態(其實就是一個while循環,不停的等待notify)。若是有請求時,空閒線程會被喚醒執行用戶的請求。

具體的請求過程是: 服務啓動時,建立一個一維線程數組(maxThread200個),並建立空閒線程(minSpareThreads5)隨時等待用戶請求。 當有用戶請求時,調用 threadpool.runIt(ThreadPoolRunnable)方法,將一個須要執行的實例傳給ThreadPool中。其中用戶須要執行的 實例必須實現ThreadPoolRunnable接口。 ThreadPool 首先查找空閒的線程,若是有則用它運行要執行ThreadPoolRunnable;若是沒有空閒線程而且沒有超過maxThreads,就一次性建立 minSpareThreads個空閒線程;若是已經超過了maxThreads了,就等待空閒線程了。總之,要找到空閒的線程,以便用它執行實例。找到 後,將該線程從線程數組中移走。 接着喚醒已經找到的空閒線程,用它運行執行實例(ThreadPoolRunnable)。 運行完ThreadPoolRunnable後,就將該線程從新放到線程數組中,做爲空閒線程供後續使用。

由此能夠看出,Tomcat的線程池實現是比較簡單的,ThreadPool.java也只有840行代碼。用一個一維數組保存空閒的線程,每次以一個較小步伐(5個)建立空閒線程並放到線程池中。使用時從數組中移走空閒的線程,用完後,再「歸還」給線程池。

tomcat5.5.10以上版本支持apr,支持經過apache runtime module進行JNI調用,使用本地代碼來加速網絡處理。

若是不使用apr以前,TomcatServlet線程池使用的是阻塞IO的模式,使用apr以後,線程池變成了 NIO的非阻塞模式,並且這種NIO仍是使用了操做系統的本地代碼,看tomcat文檔上面的說法是,極大提高web處理能力,再也不須要專門放一個web server處理靜態頁面了。

我本身直觀的感覺是,不用apr以前,你配置多少個等待線程,tomcat就會啓動多少個線程掛起等待,使用apr之後,無論你配置多少,就只有幾個NIO調度的線程,這一點你能夠經過kill -3 PID,而後察看log得知。

假設不使用apr,可能端口的線程調度能力比較差,因此經過iptables進行端口轉發,讓兩個端口去分擔一個端口的線程調度,就有可能減小線程調度的併發,從而提升處理能力,減小資源消耗。

 

所需JAR包:

tomcat-util.jar

tomcat-juli.jar

 

參考資料:

Tomat組件研究之ThreadPool - 老碼農的專欄http://blog.csdn.net/chen77716/article/details/344764

 

9、task-frame

負責線程池中任務的高效率和併發執行。
1.
您能夠自定義線程池大小,以便您能夠調整項目的性能要求。
2.
您沒必要擔憂任務運行時間過長,由於任務框架已經被實現來監視超時任務,一旦任務運行時間不長,任務監視器將從線程池中刪除它,還可幫助您自動結束超時任務。
3.
任務調度框架具備出色的性能,若是任務隊列爲空,則框架將自動進入等待狀態,直到任務隊列有要添加的任務。這將大大下降CPU的消耗。
框架很容易使用,例如:
TaskQueue queue = new TaskQueue
(); 
taskAssigner = new TaskAssigner
queue10; 
taskAssigner.start
(); 
queue.add
task;

 

參考資料:

https://sourceforge.net/projects/task-frame/?source=directory

 

10、本身實現一個Thread Pool

參考資料:

https://github.com/gauravrmazra/java-threadpool

http://geekrai.blogspot.in/2014/12/implementing-thread-pool-in-java.html

相關文章
相關標籤/搜索