1. 程式人生 > >併發程式設計的藝術-執行緒池原始碼解析

併發程式設計的藝術-執行緒池原始碼解析

執行緒池的作用:

1,降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。

2,提搞響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。

3,提高系統的客觀理性。執行緒是稀缺資源,如果無限制地建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一分配,調優和監控。但是,要做到合理利用執行緒池,必須對其實現原理了如指掌。

一,執行緒池的實現原理

當想一個執行緒池提交一個新任務時,執行緒池的處理流程如下:

1,執行緒池判斷核心執行緒池裡面的執行緒是否都在執行任務,如果核心執行緒未滿,則建立一個新的工作執行緒來執行任務。如果核心執行緒已滿,則進如下一個流程判斷。

2,執行緒池判斷工作佇列是否已經滿了,如果工作佇列沒有滿,則將新提交的任務儲存在這個工作佇列裡。如果工作佇列已經滿了,則進入下個流程。

3,執行緒池判斷執行緒池的執行緒是否都處於工作狀態,如果沒有,則建立一個新的工作執行緒來執行任務。如果已經滿了,則提交給飽和策略來處理這個任務。

ThreadPoolExecutor執行execute方法,有如下四種情況:

1,如果當前執行的執行緒少於corePoolSize,則建立新執行緒來執行任務(需要獲取全域性鎖)。

2,如果執行的執行緒等於或者多餘corePoolSize,則將任務加入BlockingQueue。

3,如果無法將任務加入BlockingQueue,則建立新的執行緒來處理任務(需要獲取全域性鎖)。

4,如果建立新執行緒將使當前執行的執行緒超出maximumPoolSize,任務將被拒絕,並呼叫RejectExecutionHandler.rejectedExecution方法。

ThreadPoolExecutor採取上述步驟的總體設計思路,是為了執行execute方法時候,儘可能避免獲取全域性鎖。在ThreadPoolExecutor完成預熱之後(即當前執行的執行緒數目大於等於核心執行緒數目),幾乎所有的execute方法呼叫的都是執行步驟2,而步驟2是不需要獲取全域性鎖的,保證了效能。

ThreadPoolExecutor的使用

執行緒池的建立

​ 我們要用執行緒池來統一分配和管理我們的執行緒,那首先我們要建立一個執行緒池出來,還是有很多大牛已經幫我們寫好了很多方面的程式碼的,Executors的工廠方法就給我們提供了建立多種不同執行緒池的方法。因為這個類只是一個建立物件的工廠,並沒有涉及到很多的具體實現,所以我不會過於詳細地去說明。

​ 老規矩,還是直接上程式碼吧。

publicstaticExecutorServicenewFixedThreadPool(intnThreads){returnnewThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,newLinkedBlockingQueue()); }

這裡也就舉出一個方法的例子來進行之後的講解吧,我們可以看出,Executors只是個工廠而已,方法也只是來例項化不同的物件,實際上例項化出來的關鍵類就是ThreadPoolExecutor。現在我們就先來簡單地對ThreadPoolExecutor建構函式內的每個引數進行解釋一下吧。

corePoolSize(核心執行緒池大小):當提交一個任務到執行緒池時,執行緒池會建立一個執行緒來執行任務,即使其他空閒的基本執行緒能夠執行新任務也會建立執行緒,當任務數大於核心執行緒數的時候就不會再建立。在這裡要注意一點,執行緒池剛建立的時候,其中並沒有建立任何執行緒,而是等任務來才去建立執行緒,除非呼叫了prestartAllCoreThreads()或者prestartCoreThread()方法 ,這樣才會預先建立好corePoolSize個執行緒或者一個執行緒。

maximumPoolSize(執行緒池最大執行緒數):執行緒池允許建立的最大執行緒數,如果佇列滿了,並且已建立的執行緒數小於最大執行緒數,則執行緒池會再建立新的執行緒執行任務。值得注意的是,如果使用了無界佇列,此引數就沒有意義了。

keepAliveTime(執行緒活動保持時間):此引數預設線上程數大於corePoolSize的情況下才會起作用, 當執行緒的空閒時間達到keepAliveTime的時候就會終止,直至執行緒數目小於corePoolSize。不過如果呼叫了allowCoreThreadTimeOut方法,則當執行緒數目小於corePoolSize的時候也會起作用.

unit(keelAliveTime的時間單位):keelAliveTime的時間單位,一共有7種,在這裡就不列舉了。

workQueue(阻塞佇列):阻塞佇列,用來儲存等待執行的任務,這個引數也是非常重要的,在這裡簡單介紹一下幾個阻塞佇列。

ArrayBlockingQueue:這是一個基於陣列結構的有界阻塞佇列,此佇列按照FIFO的原則對元素進行排序。

LinkedBlockingQueue:一個基於連結串列結構的阻塞佇列,此佇列按照FIFO排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()就是使用了這個佇列。

SynchronousQueue:一個不儲存元素的阻塞佇列。每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態。吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool()就使用了這個佇列。

PriorityBlockingQueue:一個具有優先順序的無阻塞佇列。

handler(飽和策略);當執行緒池和佇列都滿了,說明執行緒池已經處於飽和狀態了,那麼必須採取一種策略來處理還在提交過來的新任務。這個飽和策略預設情況下是AbortPolicy,表示無法處理新任務時丟擲異常。共有四種飽和策略提供,當然我們也可以選擇自己實現飽和策略。

AbortPolicy:直接丟棄並且丟擲RejectedExecutionException異常

CallerRunsPolicy:只用呼叫者所線上程來執行任務。

DiscardOldestPolicy:丟棄佇列裡最近的一個任務,並執行當前任務。

DiscardPolicy:丟棄任務並且不丟擲異常。

如何一起學習,有沒有免費資料?

在程式設計師這條路上遇到瓶頸的朋友可以加WX:daxigua012 大家一起來提升進步 但要備註好資訊 ,分享知識

關注下面公眾號"Java這點事"獲取BATJ等一線網際網路企業面試題目和答案還有java技術乾貨知識等你領取