一、簡述
在開發中,頻繁的創建和銷燬一個線程,是極耗資源的,爲此創建一個可重用指定線程數的線程池,以共享的無界隊列方式來運行這些線程,可以有效的規劃線程的使用。
線程池顧名思義,也就是線程的集合,在java中大致有這幾種線程池:
- newSingleThreadExecutor 創建一個單線程化的線程池, 它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行,可以控制線程的執行順序
- newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待,當創建的線程池數量爲1的時候。也類似於單線程化的線程池,當爲1的時候,也可控制線程的執行順序
- newCachedThreadPool 創建一個可緩存線程池,如果線程池長度超過需要的線程數量,可靈活回收空閒線程,若無可回收,則新建線程
- newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行,可以作一個定時器使用
二、用法案例
說太多原理,講太多種方式,還沒讓人明白怎麼用也是瞎BB。下面直接通過完整代碼講述newFixedThreadPool方式創建一個定長線程池的用法,然後再簡述下其它用法和原理就瞭解了。
代碼:
public class ThreadPoolTest {
public static void main(String[] args) {
try {
//調用測試線程池的使用
ThreadPoolTest threadPoolTest = new ThreadPoolTest();
threadPoolTest.callService();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 假設有8條數據需要分別放置在不同的任務線程中處理
* @throws InterruptedException
*/
public void callService() throws InterruptedException {
//創建一個定長爲3的線程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 8; i++){
Thread.sleep(100);//稍作等待
System.out.println("-------第"+i+"次for循環 -------");
int sNum = i;
fixedThreadPool.execute(new Runnable() {//線程池開啓線程任務
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("現在是線程\""+Thread.currentThread().getName()+"\",他在操作第"+sNum+"條數據----開始:"+sdf.format(new Date()));
try {
Thread.sleep(5000);//假設,處理一條數據需要5秒
} catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("現在是線程\""+Thread.currentThread().getName()+"\",他在操作第"+sNum+"條數據----結束:"+sdf.format(new Date()));
}
});
}
System.out.println("-------方法結束 -------");
}
}
輸出:
結論1:由上面的案例(創建一個定長爲3的線程池,循環執行8個任務)的輸出可以看到:
任務加入到線程池後,線程池即啓動線程執行加入的任務,當啓動線程數達到線程池的上限(這裏爲3)後,剩餘的任務會加入到線程池的等待隊列中,然後主程序繼續向下執行至"方法結束",而等待隊列中的任務會在線程池線程空閒後被依次執行;所有任務結束後,線程池並不會被關閉。
引申改進:
在開發過程中,我們常常需要等待所有線程任務執行完畢後再返回,這裏我們通過shutdown()、isTerminated()兩方法,聯合實現。
代碼:
public void callService() throws InterruptedException {
//創建一個定長爲3的線程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 8; i++){
Thread.sleep(100);//稍作等待
System.out.println("-------第"+i+"次for循環 -------");
int sNum = i;
fixedThreadPool.execute(new Runnable() {//線程池開啓線程任務
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("現在是線程\""+Thread.currentThread().getName()+"\",他在操作第"+sNum+"條數據----開始:"+sdf.format(new Date()));
try {
Thread.sleep(5000);//假設,處理一條數據需要5秒
} catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("現在是線程\""+Thread.currentThread().getName()+"\",他在操作第"+sNum+"條數據----結束:"+sdf.format(new Date()));
}
});
}
fixedThreadPool.shutdown();//關閉線程池,不再接收新任務,如果隊列中還有任務,則等待執行完畢
while (true) {
if (fixedThreadPool.isTerminated()) {//關閉後所有任務都已完成,則返回 true
System.out.println("-------線程池執行完畢 -------");
break;
}
Thread.sleep(200);//爲了避免無限制的循環判斷,讓主線程睡眠200毫秒後判斷一次(因此會造成所有任務執行完畢後,200毫秒後纔得到通知,設置時間越小,誤差越小)
}
System.out.println("-------方法結束 -------");
}
輸出:
結論二:額......沒啥了,看註解、看輸出,還不能理解就涼涼了。線程池的一些方法可以看《線程池ExecutorService的主要方法》瞭解下。
三、線程的四種用法
剛詳細的講述了一種線程池的用法,想必對線程池有一個較深的印象了,下面再回過頭簡單介紹下"簡述"中提到的4種用法,上代碼看註釋吧:
public class ThreadPoolFourTest {
/**
* newSingleThreadExecutor 創建一個單線程化的線程池,
* 它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行
*/
public static void threadPool_test_1(){
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for(int i = 1; i <= 8; i++){
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"執行了第"+index+"個任務");
}
});
}
}
/**
* newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待
*/
public static void threadPool_test_2(){
//當參數爲1的時候,可以控制線程的執行順序
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for(int i = 1; i <= 8; i++){
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"執行第"+index+"個任務");
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace();}
}
});
}
}
/**
* newCachedThreadPool創建一個可緩存線程池,
* 如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
*/
public static void threadPool_test_3(){
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i <= 8; i++) {
final int index = i;
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"執行第"+index+"個任務");
}
});
}
}
/**
* 測試newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
* 一般可做定時器使用
*/
public static void threadPool_test_4(){
System.out.println("開始設置newScheduledThreadPool啓動任務啦:"+new Date());
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
/**
* 第2個參數:首次執行該線程的延遲時間,之後失效
* 第3個參數:首次執行完之後,再過該段時間再次執行該線程,具有週期性
* 第4個參數:時間單位
*/
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("執行了newScheduledThreadPool任務:"+new Date());
}
}, 2, 3, TimeUnit.SECONDS);
}
}
結束:......好了,用法就到這裏了,上面的連接池都是由線程工具類Executors來創建的,newFixedThreadPool、newCachedThreadPool什麼的都是 new ThreadPoolExecutor出來的,只是參數不一樣,還有源碼addWorker()控制線程數啥的,有興趣的可以自己瞭解下,或者關注下,後面整理了發出來。