Java多執行緒高併發知識點二:執行緒池和CountDownLatch
執行緒池
池化是在為了複用常用的一種技術解決思路,比如連線池、執行緒池等,執行緒池主要是為了降低執行緒建立和銷燬造成的資源消耗,進而起到解決系統資源,提高系統相應速度的目的。Java中的ThreadPoolExecutor是JDK為我們提供好的執行緒池工具。
ThreadPoolExecutor executor = new ThreadPoolExecutor(50,//核心池大小
5000,//最大池大小
200,//執行緒最大空閒時間,超過此空閒時間可以被收回
TimeUnit.MILLISECONDS, //最大空閒時間的單位
new ArrayBlockingQueue<Runnable>(10)//用於儲存執行任務的佇列,10的意思是可以允許10個任務在排隊,如果佇列滿了,則建立新的執行緒。
);
向執行緒池中提交任務
Task myTask = new Task();
executor.execute(myTask);
//任務定義
class Task implements Runnable {
.....
}
executor.submit(myTask)
也可以用於提交任務,但是主要用於帶返回值的Task,用的並不多,因此不做贅述。
在此篇博文中我們用CountDownLatch來記錄任務的完成情況,完成執行緒間的通訊。
CountDownLatch
使用了多執行緒處理任務後,最常見的需要就是主執行緒並不知道任務的完成情況,因此無法決定是繼續往下進行還是繼續完畢。CountDownLatch是JDK提供給我們的多執行緒間通訊的一個工具,用於讓主執行緒知道任務完成的進度。
舉個例子來說:
目前一名老師接到了一個任務,說要把教室裡的桌子都擦乾淨
流程是:
- 把桌子擦乾淨
- 等全部桌子乾淨了之後,去敲上課鈴。
如果不用多執行緒做的話,教室裡有1000張桌子,老師(主執行緒)要逐一把桌子擦乾淨,然後去敲上課鈴。效率比較低,於是老師叫來了50個同學(50個執行緒),告訴他們要擦桌子。但是問題是老師無法檢查每張桌子是否都擦了,所以不知道什麼時候去敲上課鈴,於是約定同學擦完一張桌子就要舉手報告老師一次,老師只需要記錄同學的舉手次數,等到同學的舉手次數到了1000次,老師去敲上課鈴。而這個CountDownLatch其實就是這個計數器
主執行緒的程式碼:
//宣告,1000的意思,意味著1000個任務等待完成
CountDownLatch n = new CountDownLatch(1000);
//將1000個任務交給執行緒池去處理
for(int i=0;i<1000;i++){
//啟動一個任務
Task myTask = new Task(i,n);
executor.execute(myTask);
}
//等待所有執行緒完畢
try {
n.await();
} catch (InterruptedException e) {
logger.error(e);
}
//去敲上課鈴
任務的程式碼:
public void run() {
//做你的事情,擦桌子
//....
//事情幹完了
n.countDown();
}
當然,除了CountDownLatch,多執行緒通訊中還存在CyclicBarrier和Semaphore等,但是CountDownLatch是其中最常用也最簡單的一種方式,其他的方式,大家自行搜尋理解即可。
高併發示例程式碼,
最後附上我經常用的做簡單的多執行緒測試時,模擬高併發時的程式碼,希望能節約大家一點時間。
Tester.java
package com.test;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import java.util.concurrent.*;
public class Tester {
private static Logger logger = LogManager.getLogger(Tester.class);
public void doSome(int num){
//啟用執行緒池(最大不能超過500個)
ThreadPoolExecutor executor = new ThreadPoolExecutor(50,//核心池大小
5000,//最大池大小
200,//執行緒最大空閒時間,超過此空閒時間可以被收回
TimeUnit.MILLISECONDS, //最大空閒時間的單位
new ArrayBlockingQueue<Runnable>(10)//用於儲存執行任務的佇列,可以選擇其他不同的佇列來做任務管理
);
CountDownLatch n = new CountDownLatch(num);
for(int i=0;i<num;i++){
//啟動一個任務
Task myTask = new Task(i,n);
executor.execute(myTask);
}
logger.info("全部執行的任務數量:"+executor.getTaskCount());
logger.info("已完成的任務數量:"+executor.getCompletedTaskCount());
logger.info("曾經建立過最大的執行緒數量:"+executor.getPoolSize());
logger.info("活動的執行緒數量:"+executor.getPoolSize());
//等待所有執行緒完畢
try {
n.await();
} catch (InterruptedException e) {
logger.error(e);
}
//關閉執行緒池
executor.shutdown();
}
class Task implements Runnable {
private int taskNum;
CountDownLatch n;
public Task(int num,CountDownLatch n) {
this.taskNum = num;
this.n= n;
}
public void run() {
try{
//TODO 做你的事情,擦桌子
}catch(Exception e){
logger.error(e);
logger.info("task "+taskNum+"執行失敗");
}
//事情幹完了
n.countDown();
}
}
}