簡單介紹線程池在併發編程中的使用

編程語言 Java 技術 合法程序媛 合法程序媛 2017-09-19

本文將從為什麼要使用線程池以及怎樣使用線程池兩方面來介紹線程池在併發編程中的使用。

簡單介紹線程池在併發編程中的使用

一、為什麼要使用線程池

當需要處理的任務較少時,我們可以自己創建線程去處理,但在高併發場景下,我們需要處理的任務數量很多,由於創建銷燬線程開銷很大,這樣頻繁創建線程就會大大降低系統的效率。此時,我們就可以使用線程池,線程池中的線程執行完一個任務後可以複用,並不被銷燬。合理使用線程池有以下幾點好處:

1、減少資源的開銷。通過複用線程,降低創建銷燬線程造成的消耗。

2、多個線程併發執行任務,提高系統的響應速度。

3、可以統一的分配,調優和監控線程,提高線程的可管理性。

二、怎樣使用線程池

1、創建線程池

通過ThreadPoolExecutor來創建一個線程池。new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, threadFactory, handler);

java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個類,它有四個構造方法。

public class ThreadPoolExecutor extends AbstractExecutorService {

.....

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

...

}

由源碼可知,前三個構造器都是調用第四個構造器進行初始化。

各參數含義:

. corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時就不再創建。如果調用了線程池的prestartAllCoreThreads方法,線程池會提前創建並啟動所有基本線程。

. maximumPoolSize(線程池最大大小):線程池允許創建的最大線程數。如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得注意的是如果使用了無界的任務隊列這個參數就沒什麼效果。

. keepAliveTime(線程活動保持時間):線程池的工作線程空閒後,保持存活的時間。所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,提高線程的利用率。

. TimeUnit(線程活動保持時間的單位):可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

. runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列。可以選擇以下幾個阻塞隊列。

(1) ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。

(2) LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。

(3) SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。

(4) PriorityBlockingQueue:一個具有優先級得無限阻塞隊列。

.ThreadFactory:用於設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字,Debug和定位問題時非常又幫助。

.Reje、ctedExecutionHandler(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。以下是JDK1.5提供的四種策略。n AbortPolicy:直接拋出異常。

(1) CallerRunsPolicy:只用調用者所在線程來運行任務。

(2) DiscardOldestPolicy:丟棄隊列裡最近的一個任務,並執行當前任務。

(3) DiscardPolicy:不處理,丟棄掉。

(4) 當然也可以根據應用場景需要來實現RejectedExecutionHandler接口自定義策略。如記錄日誌或持久化不能處理的任務。

2、向線程池提交任務

使用execute提交任務,但execute()方法沒有返回值,無法判斷任務是否被線程池執行成功。execute方法輸入的任務是一個Runnable類的實例。

execute(new Runnable() {

@Override

public void run() {

// TODO Auto-generated method stub

}

});

使用submit()方法提交任務,會返回一個future,那麼我們可以通過這個future來判斷任務是否執行成功。

3、線程池的關閉

調用shutdown()和shutdownNow()方法關閉線程池。

4、使用示例

這裡我們選用第一種構造方法演示。

public class ThreadPoolTest {

public static void main(String[] args) {

ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));

for(int i=0;i<15;i++){

Task task = new Task(i);

executor.execute(task);

System.out.println("線程池中線程數:" + executor.getPoolSize() + ",隊列中等待執行的任務數:" + executor.getQueue().size() +

",已執行完的任務數:" + executor.getCompletedTaskCount());

}

executor.shutdown();

}

}

class Task implements Runnable {

private int taskNum;

public Task(int num) {

this.taskNum = num;

}

@Override

public void run() {

System.out.println("task"+taskNum+“開始執行”);

try{

Thread.currentThread().sleep(4000);

}catch(InterruptedException e){

e.printStackTrace();

}

System.out.println("task"+taskNum+“執行完畢”);

}

}

}

執行結果:

task0開始執行

線程池中線程數:1,隊列中等待執行的任務數:0,已執行完的任務數:0

線程池中線程數:2,隊列中等待執行的任務數:0,已執行完的任務數:0

task1開始執行

線程池中線程數:3,隊列中等待執行的任務數:0,已執行完的任務數:0

task2開始執行

線程池中線程數:4,隊列中等待執行的任務數:0,已執行完的任務數:0

task3開始執行

線程池中線程數:5,隊列中等待執行的任務數:0,已執行完的任務數:0

task4開始執行

線程池中線程數:5,隊列中等待執行的任務數:1,已執行完的任務數:0

線程池中線程數:5,隊列中等待執行的任務數:2,已執行完的任務數:0

線程池中線程數:5,隊列中等待執行的任務數:3,已執行完的任務數:0

線程池中線程數:5,隊列中等待執行的任務數:4,已執行完的任務數:0

線程池中線程數:5,隊列中等待執行的任務數:5,已執行完的任務數:0

線程池中線程數:6,隊列中等待執行的任務數:5,已執行完的任務數:0

task10開始執行

線程池中線程數:7,隊列中等待執行的任務數:5,已執行完的任務數:0

task11開始執行

線程池中線程數:8,隊列中等待執行的任務數:5,已執行完的任務數:0

task12開始執行

線程池中線程數:9,隊列中等待執行的任務數:5,已執行完的任務數:0

task13開始執行

線程池中線程數:10,隊列中等待執行的任務數:5,已執行完的任務數:0

task14開始執行

task5開始執行

task6開始執行

task7開始執行

task8開始執行

task9開始執行

由執行結果可以看出,當線程池中線程的數目大於5時,便將任務放入任務緩存隊列裡面,當任務緩存隊列滿了之後,便創建新的線程,直到線程數等於最大線程數10後,便不再創建新線程。

如果上面程序中,將for循環中改成執行20個任務,就會拋出任務拒絕異常了,因為最大線程10加上緩衝隊列5,最多接受15個任務。

在java中,並不提倡直接使用ThreadPoolExecutor,而是使用Executors類中提供的幾個靜態方法來創建線程池,其內部也是調用ThreadPoolExecutor的構造方法,只不過參數已提前配置:

//corePoolSize設置為0,將maximumPoolSize設置為Integer.MAX_VALUE,使用的SynchronousQueue。

Executors.newCachedThreadPool();

//corePoolSize和maximumPoolSize都設置為1,使用的LinkedBlockingQueue

Executors.newSingleThreadExecutor();

//corePoolSize和maximumPoolSize值是相等的,使用的LinkedBlockingQueue

Executors.newFixedThreadPool(int);  

實際開發中,如能滿足要求,儘量使用這三個靜態方法。如ThreadPoolExecutor達不到要求,可自己繼承ThreadPoolExecutor類進行重寫。

最後要感謝這個優秀的平臺,可以讓我們相互交流,如果想進一步學習交流,可以加群460570824,希望大家可以一起學習進步!

相關推薦

推薦中...