JUC(4)---java執行緒池原理及原始碼分析

白露非霜發表於2020-05-24

執行緒池,既然是個池子裡面肯定就裝很多執行緒。

如果併發的請求數量非常多,但每個執行緒執行的時間很短,這樣就會頻繁的建立和銷燬 執行緒,如此一來會大大降低系統的效率。可能出現伺服器在為每個請求建立新執行緒和銷燬線 程上花費的時間和消耗的系統資源要比處理實際的使用者請求的時間和資源更多。因此Java中提供執行緒池對執行緒進行統一的管理使用。

執行緒池可以讓多個任務重用執行緒。減少執行緒建立,消亡的開銷,提高效能。當任務達到不需要等待執行緒建立便可立即執行。提高執行緒的可管理性。使用執行緒池進行統一的分配,調優和監控。

 

Executor框架體系:

Executor介面是執行緒池框架中最頂級的介面,定義了一個用於執行Runnable的execute方法

體系簡要類圖:

 

 ExecutorService也是一個重要的介面,其中也定義了一系列重要的方法:

  1. submit(task):可用來提交Callable或Runnable任務,並返回代表此任務的Future 物件
  2. shutdown():在完成已提交的任務後封閉辦事,不再接管新任務
  3. shutdownNow():停止所有正在履行的任務並封閉辦事。
  4. isTerminated():測試是否所有任務都履行完畢了。
  5. isShutdown():測試是否該ExecutorService已被關閉。.
  6. invokeXXX(task,...):執行給定任務

從類圖中可以看到一個很重要的實現類就是ThreadPoolExecutor,我們通過這個類看下執行緒池中比較重要的一些屬性。

 

 

 

ctl 是對執行緒池的執行狀態和執行緒池中有效執行緒的數量進行控制的一個欄位, 它包含兩部分的資訊: 執行緒池的執行狀態 (runState) 和執行緒池內有效執行緒的數量 (workerCount),這裡可以看到,使用了Integer型別來儲存,高3位儲存runState,低29位儲存
workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位減1(29個1),這個常量表示workerCount的上限值,大約是5.3億。
執行緒池五種狀態:

RUNNING:ctl高三位111,可以接受新任務,並且處理已經新增的任務。執行緒池的初始化狀態就是running。

 

SHUTDOWN:ctl高三位000,不接受新任務,但是可以處理已經新增的任務。呼叫shutdown()方法,由running變成shutdown

 

STOP:ctl高三位001,不接受新任務,不處理已新增的任務,並且還會中斷正在處理的任務。呼叫shutdownNow()方法,由running/shutdown變成stop

 

TIDYING:ctl高三位010,當所有的任務已終止,ctl記錄的任務數量為0,執行緒池會變為tidying 狀態。當執行緒池變為tidying 狀態時,會執行鉤子函式terminated()。terminated()在 ThreadPoolExecutor類中是空的,若使用者想線上程池變為tidying 時,進行其他處理;可以通過重寫terminated()。執行緒池為shutdown狀態,並且阻塞佇列是空的,並且執行的任務也是空就會由shutdown變成tidying;stop狀態時,執行緒池中任務為空也會變成tidying

 

TERMINATED:ctl高三位011,執行緒池徹底涼涼了

當執行緒池處於tidying狀態並且執行完了terminated()方法,就會由tidying變成terminated

 

構造方法:

 

corePoolSize執行緒池中的核心執行緒數,當提交一個任務時,執行緒池建立一個新執行緒執行任務,直到當 前執行緒數等於corePoolSize;如果當前執行緒數為corePoolSize,繼續提交的任務被儲存到 阻塞佇列中,等待被執行;如果執行了執行緒池的prestartAllCoreThreads()方法,執行緒池會 提前建立並啟動所有核心執行緒。

maximumPoolSize執行緒池中允許的最大執行緒數。如果當前阻塞佇列滿了,且繼續提交任務,則建立新的執行緒執行任務,前提是當前執行緒數小於maximumPoolSize;

keepAliveTime執行緒池維護執行緒所允許的空閒時間。當執行緒池中的執行緒數量大於corePoolSize的時候,如果這時沒有新的任務提交,核心執行緒外的執行緒不會立即銷燬,而是會等待,直到等待的時間超過了keepAliveTime

unitkeepAliveTime的單位 

workQueue用來儲存等待被執行的任務的阻塞佇列,且任務必須實現Runable介面,在JDK中提供瞭如下阻塞佇列:

  1、ArrayBlockingQueue:基於陣列結構的有界阻塞佇列,按FIFO排序任務;

   2、LinkedBlockingQuene:基於連結串列結構的阻塞佇列,按FIFO排序任務,

  3、SynchronousQuene:一個不儲存元素的阻塞佇列,每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態

   4、priorityBlockingQuene:具有優先順序的無界阻塞佇列;

threadFactory ThreadFactory,用來建立新執行緒。預設使用 Executors.defaultThreadFactory() 來建立執行緒。使用預設的ThreadFactory來建立執行緒時,會使新建立的執行緒具有相同的NORM_PRIORITY優先順序並且是非守護執行緒,同時也設定了執行緒的名稱。

Handler執行緒池的拒絕策略,當阻塞佇列滿了,且沒有空閒的工作執行緒,如果繼續提交任務,必須採取一種策略處理該任務,執行緒池提供了4種策略:

  1、AbortPolicy:直接丟擲異常,預設策略;

  2、CallerRunsPolicy:用呼叫者所在的執行緒來執行任務;

  3、DiscardOldestPolicy:丟棄阻塞佇列中靠最前的任務,並執行當前任務;

  4、DiscardPolicy:直接丟棄任務;

上面的4種策略都是ThreadPoolExecutor的內部類。 當然也可以根據應用場景實現RejectedExecutionHandler介面,自定義策略

 

通過execute或者submit向執行緒池提交任務。任務提交的時候先提交給核心執行緒(corePoolSize);如果核心執行緒滿了,就將任務放到workQueue裡面去排隊等待;如果佇列也滿了(取決用的什麼佇列,以及設定的大小),就會將新進來的任務提交給非核心執行緒,非核心執行緒數量等於maximumPoolSize - corePoolSize,非核心執行緒使用之後會被回收。如果非核心執行緒也滿了,那麼就執行相應的拒絕策略RejectedExecutionHandler

 

 

原始碼解析:

1.execute方法:

 

 

 2. 可以看到關鍵方法是addWork方法,可以看到在addWork方法先會進行一系列判斷,如果都通過了,才會進行任務的建立

 

 3.Worker內部類,這類是繼承了AbstractQueuedSynchronizer並且實現了Runable介面,其中還有兩個重要屬性一個是Thread, 一個是firstTask,初始化的時候會吧AQS中的state欄位設定-1,後面允許中斷會將這個值修改。

會通過執行緒工廠建立一個執行緒和當前的worker繫結,建立執行緒的runable介面物件就是work本身。worker重寫的run方法實際呼叫了執行緒池的runWorker方法

 

 

 4. 回到addWorker方法,建立完了worker,可以就可以獲取到繫結的執行緒了,將worker新增到工作執行緒的集合中去。然後呼叫物件繫結執行緒的start方法,實際上會呼叫到worker的run方法,進而呼叫執行緒池的runWorker方法

 5. runWorker 方法中會去worker中取任務,如果firstTask空,就去佇列中取,因為之前在addwork的時候有些場景傳入的firstWork是null。從佇列取不到任務了,也就是getTask返回null了,結束while迴圈,呼叫processWorkerExit方法移除任務,處理最終的一些狀態轉換

 

 

 

就是說通過執行緒呼叫worker的run方法,然後藉助worker裡面再來呼叫我們傳入執行緒池中的任務的run方法。所以說其實是起了一些執行緒,然後呼叫run方法一直嘗試去取任務,取到之後手動呼叫的run方法執行任務。

相關文章