Java 線程池藝術探索

文章來自:http://www.54tianzhisheng.cn/2017/07/29/ThreadPool/

Wiki 上是這樣解釋的:Thread Pool

作用:利用線程池可以大大減少在創建和銷毀線程上所花的時間以及系統資源的開銷!

下面主要講下線程池中最重要的一個類 ThreadPoolExecutor 。

ThreadPoolExecutor

ThreadPoolExecutor 構造器:

有四個構造器的,挑了參數最長的一個進行講解。

七個參數:

corePoolSize:核心池的大小,在創建了線程池后,默認情況下,線程池中并沒有任何線程,而是等待有任務到來才創建線程去執行任務,當有任務來之后,就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize后,就會把到達的任務放到緩存隊列當中;

maximumPoolSize:線程池最大線程數;

keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止;

unit:參數keepAliveTime的時間單位(DAYS、HOURS、MINUTES、SECONDS 等);

workQueue:阻塞隊列,用來存儲等待執行的任務;

ArrayBlockingQueue (有界隊列)

LinkedBlockingQueue (無界隊列)

SynchronousQueue

threadFactory:線程工廠,主要用來創建線程

handler:拒絕處理任務的策略

AbortPolicy:丟棄任務并拋出 RejectedExecutionException 異常。(默認這種)

DiscardPolicy:也是丟棄任務,但是不拋出異常

DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執行任務(重復此過程)

CallerRunsPolicy:由調用線程處理該任務

重要方法:

execute():通過這個方法可以向線程池提交一個任務,交由線程池去執行;

shutdown():關閉線程池;

execute() 方法:

注:JDK 1.7 和 1.8 這個方法有點區別,下面代碼是 1.8 中的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

publicvoidexecute(Runnable command){

if(command ==null)

thrownewNullPointerException();

intc = ctl.get();

//1、如果當前的線程數小于核心線程池的大小,根據現有的線程作為第一個 Worker 運行的線程,新建一個 Worker,addWorker 自動的檢查當前線程池的狀態和 Worker 的數量,防止線程池在不能添加線程的狀態下添加線程

if(workerCountOf(c) < corePoolSize) {

if(addWorker(command,true))

return;

c = ctl.get();

}

//2、如果線程入隊成功,然后還是要進行 double-check 的,因為線程在入隊之后狀態是可能會發生變化的

if(isRunning(c) && workQueue.offer(command)) {

intrecheck = ctl.get();

// recheck 防止線程池狀態的突變,如果突變,那么將 reject 線程,防止 workQueue 中增加新線程

if(! isRunning(recheck) && remove(command))

reject(command);

elseif(workerCountOf(recheck) ==0)//上下兩個操作都有 addWorker 的操作,但是如果在workQueue.offer 的時候 Worker 變為 0,那么將沒有 Worker 執行新的 task,所以增加一個 Worker.

addWorker(null,false);

}

//3、如果 task 不能入隊(隊列滿了),這時候嘗試增加一個新線程,如果增加失敗那么當前的線程池狀態變化了或者線程池已經滿了然后拒絕task

elseif(!addWorker(command,false))

reject(command);

}

其中調用了 addWorker() 方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

privatebooleanaddWorker(Runnable firstTask,booleancore){// firstTask: 新增一個線程并執行這個任務,可空,增加的線程從隊列獲取任務;core:是否使用 corePoolSize 作為上限,否則使用 maxmunPoolSize

retry:

for(;;) {

intc = ctl.get();

intrs = runStateOf(c);

// Check if queue empty only if necessary.

/**

* rs!=Shutdown || fistTask!=null || workQueue.isEmpty

* 如果當前的線程池的狀態 > SHUTDOWN 那么拒絕 Worker 的 add 如果 =SHUTDOWN

* 那么此時不能新加入不為 null 的 Task,如果在 workQueue 為 empty 的時候不能加入任何類型的 Worker,

* 如果不為 empty 可以加入 task 為 null 的 Worker, 增加消費的 Worker

*/

if(rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask ==null&&! workQueue.isEmpty()))

returnfalse;

for(;;) {

intwc = workerCountOf(c);

//如果當前的數量超過了 CAPACITY,或者超過了 corePoolSize 和 maximumPoolSize(試 core 而定)

if(wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))

returnfalse;

//CAS 嘗試增加線程數,如果失敗,證明有競爭,那么重新到 retry。

if(compareAndIncrementWorkerCount(c))// AtomicInteger 的 CAS 操作;

breakretry;

c = ctl.get();// Re-read ctl

//判斷當前線程池的運行狀態,狀態發生改變,重試 retry;

if(runStateOf(c) != rs)

continueretry;

// else CAS failed due to workerCount change; retry inner loop

}

}

booleanworkerStarted =false;

booleanworkerAdded =false;

Worker w =null;

try{

w =newWorker(firstTask);// Worker 為內部類,封裝了線程和任務,通過 ThreadFactory 創建線程,可能失敗拋異常或者返回 null

finalThread t = w.thread;

if(t !=null) {

finalReentrantLock mainLock =this.mainLock;

mainLock.lock();

try{

// Recheck while holding lock.

// Back out on ThreadFactory failure or if

// shut down before lock acquired.

intrs = runStateOf(ctl.get());

if(rs < SHUTDOWN ||

(rs == SHUTDOWN && firstTask ==null)) {

if(t.isAlive())// precheck that t is startable

// SHUTDOWN 以后的狀態和 SHUTDOWN 狀態下 firstTask 為 null,不可新增線程

thrownewIllegalThreadStateException();

workers.add(w);

ints = workers.size();

if(s > largestPoolSize)

largestPoolSize = s;//記錄最大線程數

workerAdded =true;

}

}finally{

mainLock.unlock();

}

if(workerAdded) {

t.start();

workerStarted =true;

}

}

}finally{

if(! workerStarted)

addWorkerFailed(w);//失敗回退,從 wokers 移除 w, 線程數減一,嘗試結束線程池(調用tryTerminate 方法)

}

returnworkerStarted;

}

示意圖:

執行流程:

1、當有任務進入時,線程池創建線程去執行任務,直到核心線程數滿為止

2、核心線程數量滿了之后,任務就會進入一個緩沖的任務隊列中

當任務隊列為無界隊列時,任務就會一直放入緩沖的任務隊列中,不會和最大線程數量進行比較

當任務隊列為有界隊列時,任務先放入緩沖的任務隊列中,當任務隊列滿了之后,才會將任務放入線程池,此時會與線程池中最大的線程數量進行比較,如果超出了,則默認會拋出異常。然后線程池才會執行任務,當任務執行完,又會將緩沖隊列中的任務放入線程池中,然后重復此操作。

shutdown() 方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

publicvoidshutdown(){

finalReentrantLock mainLock =this.mainLock;

mainLock.lock();

try{

//判斷是否可以操作目標線程

checkShutdownAccess();

//設置線程池狀態為 SHUTDOWN, 此處之后,線程池中不會增加新 Task

advanceRunState(SHUTDOWN);

//中斷所有的空閑線程

interruptIdleWorkers();

onShutdown();// hook for ScheduledThreadPoolExecutor

}finally{

mainLock.unlock();

}

//轉到 Terminate

tryTerminate();

}

參考資料:深入理解java線程池—ThreadPoolExecutor

JDK 自帶四種線程池分析與比較

1、newFixedThreadPool

創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。

2、newSingleThreadExecutor

創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

3、newCachedThreadPool

創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。

4、newScheduledThreadPool

創建一個定長線程池,支持定時及周期性任務執行。

四種線程池其實內部方法都是調用的 ThreadPoolExecutor 類,只不過利用了其不同的構造器方法而已(傳入自己需要傳入的參數),那么利用這個特性,我們自己也是可以實現自己定義的線程池的。

自定義線程池

1、創建任務類

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

packagecom.zhisheng.thread.threadpool.demo;

/**

* Created by 10412 on 2017/7/24.

* 任務

*/

publicclassMyTaskimplementsRunnable

{

privateinttaskId;//任務 id

privateString taskName;//任務名字

publicintgetTaskId(){

returntaskId;

}

publicvoidsetTaskId(inttaskId){

this.taskId = taskId;

}

publicStringgetTaskName(){

returntaskName;

}

publicvoidsetTaskName(String taskName){

this.taskName = taskName;

}

publicMyTask(inttaskId, String taskName){

this.taskId = taskId;

this.taskName = taskName;

}

@Override

publicvoidrun(){

System.out.println("當前正在執行 ******? 線程Id-->"+ taskId +",任務名稱-->"+ taskName);

try{

Thread.currentThread().sleep(5*1000);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println("線程Id-->"+ taskId +",任務名稱-->"+ taskName +"? -----------? 執行完畢!");

}

}

2、自定義拒絕策略,實現 RejectedExecutionHandler 接口,重寫 rejectedExecution 方法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

packagecom.zhisheng.thread.threadpool.demo;

importjava.util.concurrent.RejectedExecutionHandler;

importjava.util.concurrent.ThreadPoolExecutor;

/**

* Created by 10412 on 2017/7/24.

* 自定義拒絕策略,實現 RejectedExecutionHandler 接口

*/

publicclassRejectedThreadPoolHandlerimplementsRejectedExecutionHandler

{

publicRejectedThreadPoolHandler(){

}

@Override

publicvoidrejectedExecution(Runnable r, ThreadPoolExecutor executor){

System.out.println("WARNING 自定義拒絕策略: Task "+ r.toString() +" rejected from "+ executor.toString());

}

}

3、創建線程池

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

packagecom.zhisheng.thread.threadpool.demo;

importjava.util.concurrent.ArrayBlockingQueue;

importjava.util.concurrent.ThreadPoolExecutor;

importjava.util.concurrent.TimeUnit;

/**

* Created by 10412 on 2017/7/24.

*/

publicclassThreadPool

{

publicstaticvoidmain(String[] args){

//核心線程數量為 2,最大線程數量 4,空閑線程存活的時間 60s,有界隊列長度為 3,

//ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));

//核心線程數量為 2,最大線程數量 4,空閑線程存活的時間 60s, 無界隊列,

//ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());

//核心線程數量為 2,最大線程數量 4,空閑線程存活的時間 60s,有界隊列長度為 3, 使用自定義拒絕策略

ThreadPoolExecutor pool =newThreadPoolExecutor(2,4,60, TimeUnit.SECONDS,

newArrayBlockingQueue(3),newRejectedThreadPoolHandler());

for(inti =1; i <=10; i++) {

//創建 10 個任務

MyTask task =newMyTask(i,"任務"+ i);

//運行

pool.execute(task);

System.out.println("活躍的線程數:"+pool.getActiveCount() +",核心線程數:"+ pool.getCorePoolSize() +",線程池大小:"+ pool.getPoolSize() +",隊列的大小"+ pool.getQueue().size());

}

//關閉線程池

pool.shutdown();

}

}

這里運行結果就不截圖了,我在本地測試了代碼是沒問題的,感興趣的建議還是自己跑一下,然后分析下結果是不是和前面分析的一樣,如有問題,請在我博客下面評論!

總結

本文一開始講了線程池的介紹和好處,然后分析了線程池中最核心的 ThreadPoolExecutor 類中構造器的七個參數的作用、類中兩個重要的方法,然后在對比研究了下 JDK 中自帶的四種線程池的用法和內部代碼細節,最后寫了一個自定義的線程池。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容