在并發編程中線程池的使用

在并發編程中線程池的使用

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

? 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, BlockingQueueworkQueue);

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

?? ? ? ? ? ? ? BlockingQueueworkQueue,ThreadFactory threadFactory);

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

? ? ? ? ? ? ? ? BlockingQueueworkQueue,RejectedExecutionHandler handler);?

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

? ? ? ? ? ? ? ? BlockingQueueworkQueue,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(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類進行重寫。

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

推薦閱讀更多精彩內容