線程池的使用
使用線程池管理線程可以最大程度的利用線程,節省資源消耗,它通過利用已有的線程多次循環執行多個任務從而提高系統的處理能力。
我們可以通過java.util.concurrent.ThreadPoolExecutor類來創建線程池,一個任務通過 execute(Runnable)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是Runnable類型對象的run()方法。
下面介紹一下里面的一些參數。
1、創建一個線程池需要輸入幾個參數:
· corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時就不再創建。如果調用了線程池的prestartAllCoreThreads方法,線程池會提前創建並啓動所有基本線程。
· runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列。 可以選擇以下幾個阻塞隊列。
o ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。
o LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
o SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。
o PriorityBlockingQueue:一個具有優先級的無限阻塞隊列。
· maximumPoolSize(線程池最大大小):線程池允許創建的最大線程數。如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得注意的是如果使用了無界的任務隊列這個參數就沒什麼效果。
· ThreadFactory:用於設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字。
· RejectedExecutionHandler(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。以下是JDK1.5提供的四種策略。
o AbortPolicy:直接拋出異常。
o CallerRunsPolicy:只用調用者所在線程來運行任務。
o DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
o DiscardPolicy:不處理,丟棄掉。
o 當然也可以根據應用場景需要來實現RejectedExecutionHandler接口自定義策略。如記錄日誌或持久化不能處理的任務。
· keepAliveTime(線程活動保持時間):線程池的工作線程空閒後,保持存活的時間。所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,提高線程的利用率。
· TimeUnit(線程活動保持時間的單位):可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),秒(SECONDS),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
2、當一個任務通過execute(Runnable)方法欲添加到線程池時,會有如下幾種情況:
· 如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。
· 如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
· 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。
· 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
· 當線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。
3、舉一個簡單的例子:
創建 TestThreadPool 類:
1. importjava.util.concurrent.ArrayBlockingQueue;
2. importjava.util.concurrent.ThreadPoolExecutor;
3. import java.util.concurrent.TimeUnit;
4.
5. public class TestThreadPool {
6.
7. private static int produceTaskSleepTime =2;
8.
9. private static int produceTaskMaxNumber = 2;
10.
11. public static void main(String[] args){
12.
13. // 構造一個線程池
14. ThreadPoolExecutor threadPool = newThreadPoolExecutor(2, 4, 3,
15. TimeUnit.SECONDS, newArrayBlockingQueue<Runnable>(3),
16. newThreadPoolExecutor.DiscardOldestPolicy());
17.
18. for (int i = 1; i <=produceTaskMaxNumber; i++) {
19. try {
20. String task = "task@" + i;
21. System.out.println("創建任務並提交到線程池中:" +task);
22. threadPool.execute(newThreadPoolTask(task));
23.
24. Thread.sleep(produceTaskSleepTime);
25. } catch (Exception e) {
26. e.printStackTrace();
27. }
28. }
29. }
30. }
創建 ThreadPoolTask類:
31. public class ThreadPoolTask implementsRunnable, Serializable {
32.
33. private Object attachData;
34.
35. ThreadPoolTask(Object tasks) {
36. this.attachData = tasks;
37. }
38.
39. public void run() {
40.
41. System.out.println("開始執行任務:" +attachData);
42.
43. attachData = null;
44. }
45.
46. public Object getTask() {
47. return this.attachData;
48. }
49. }
執行結果:
創建任務並提交到線程池中:task@ 1
開始執行任務:task@ 1
創建任務並提交到線程池中:task@ 2
開始執行任務:task@ 2
4、再講一個實際的例子
這裏用的是spring的線程池:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor。
直接上代碼:
50. //是否中斷導入數據程序
51. publicstatic AtomicBoolean terminateImport = newAtomicBoolean(false);
52. //成功條數
53. public static AtomicLong success_line = newAtomicLong();
54. //失敗條數
55. publicstatic AtomicLong error_line = new AtomicLong();
56. //是否發現有線程rejected,如果有,說明線程隊列滿了,外部執行代碼暫時中斷隊列填充,等待5s後,重新填充.
57. publicstatic AtomicBoolean found_rejected = new AtomicBoolean(false);
58. //開始執行
59. publicvoid startSwitch(int itemsPerPage, String updateMode,String applySeqno, Date startDate,Date endDate) {
60. int nowPage = 1;
61. int exec_sum = 0 ;
62. boolean hasNext = true ;
63. //重置靜態值
64. success_line = new AtomicLong(0L);
65. error_line = new AtomicLong(0L);
66. terminateImport = new AtomicBoolean(false);
67.
68. ThreadPoolTaskExecutor threadPoolTaskExecutor= new ThreadPoolTaskExecutor();
69. threadPoolTaskExecutor.setCorePoolSize(100);
70. threadPoolTaskExecutor.setMaxPoolSize(200);
71. threadPoolTaskExecutor.setQueueCapacity(100);
72. threadPoolTaskExecutor.setRejectedExecutionHandler(newRejectedExecutionHandler() {
73. @Override
74. public voidrejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
75. logger.warn("findRejectedExecutionException error !!,will set found_rejected boolean is false");
76. found_rejected.set(true);
77. threadPoolExecutor.execute(runnable);
78. }
79. });
80. //設成true後當調用shutdown()關閉線程池時會去檢查任務是否都執行完畢
81. threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
82. threadPoolTaskExecutor.initialize();
83.
84. while (hasNext) {
85. …
86. // applyDTOs是調接口進行分頁查詢得到的List集合,就是上面”…”省略的部分
87. //如果當次取出的總數比分頁數還要小,那麼下次進來直接中斷掉這個循環
88. if(itemsPerPage>applyDTOs.size()){
89. hasNext = false ;
90. }
91. if(ListUtil.isNotBlank(applyDTOs)){
92. if(found_rejected.get()==false){
93. if(ApplyInfoSync.terminateImport.get()==false){
94. ApplySyncDataThread applySyncDataThread= new ApplySyncDataThread(
95. convert2LoanApplyInfoDOList(applyDTOs),updateMode, loanApplyComponent);
96. threadPoolTaskExecutor.execute(applySyncDataThread);
97. }else{
98. hasNext= false;
99. LogUtil.warn(logger, "主線程發現中斷,開始嘗試關閉線程池新進入數據");
100. }
101. }else{
102. LogUtil.warn(logger,"執行被拒絕,隊列已滿了,需要等待5秒");
103. try {
104. TimeUnit.MILLISECONDS.sleep(5000L);
105. found_rejected.set(false);
106. } catch(InterruptedException e) {
107. LogUtil.warn(e,logger,"foundrejected thread,Thread Sleep is error !!");
108. }
109. }
110. }
111. exec_sum= exec_sum+applyDTOs.size();
112. if(hasNext)
113. nowPage++;
114. }
115.
116. while (threadPoolTaskExecutor.getActiveCount()>0){
117. try {
118. logger.warn("threadPool ishave active count ,now sleep...");
119. TimeUnit.MILLISECONDS.sleep(60000L);
120. } catch (InterruptedException e) {
121. logger.warn("Thread Sleepis error !!", e);
122. }
123. }
124. threadPoolTaskExecutor.shutdown();
125.
126. //檢查線程池是否完全關閉,否則先等一等再走到日誌打印階段
127. while (threadPoolTaskExecutor.getThreadPoolExecutor().isTerminated()== false) {
128. try {
129. TimeUnit.MILLISECONDS.sleep(6000L);
130. } catch (InterruptedException e) {
131. LogUtil.error(e,logger,"ThreadSleep is error !!");
132. }
133. }
134. …//一些打印日誌的操作
135. }
這裏其他的不多說了,就說幾點使容易看得懂。我這裏有兩個屬性:terminateImport和found_rejected,他們都是AtomicBoolean類型(傳入的boolean變量會被轉換爲volatile的int值,true爲1,false爲0)的都被static修飾,所以都只有一份且是同步安全的。terminateImport在一開始調用startSwitch時被重置爲false,表明沒有被中斷,當在執行的過程中,有其他的入口調用terminateImport.set(true)後,程序再執行到if(ApplyInfoSync.terminateImport.get()==false)就會是false的,從而達到中斷的作用;found_rejected用來標記任務是否被拒絕,經過之前的對線程池的介紹,應該知道了當達到最大線程數,任務隊列也滿了的時候,再execute的任務會被拒絕,所以我在threadPoolTaskExecutor.setRejectedExecutionHandler(…)設置飽和策略時自己定義實現了一個RejectedExecutionHandler接口來自定義策略,在裏面把found_rejected標記改爲了true,這樣當程序再次執行到if(found_rejected.get()==false)就會是false的,從而可以監視線程和隊列是否已經滿了,當滿了的時候,我就等待5秒,然後把found_rejected標記改爲了false就可以繼續填充隊列了,至於裏面爲什麼還要執行一次threadPoolExecutor.execute(runnable);是因爲如果不執行被拒絕的任務,那任務就被拋棄了,當然你也可以做其他操作,例如持久化任務(就是存入數據庫)等。