天天看點

JAVA多線程 —— 4.線程池

為什麼需要線程池

1、使用線程池可以重複利用已有的線程繼續執行任務,避免線程在建立和

銷毀時造成的消耗

2、由于沒有線程建立和銷毀時的消耗,可以提高系統響應速度

3、通過線程可以對線程進行合理的管理,根據系統的承受能力調整可運作

線程數量的大小等

![[Pasted image 20210521173731.png]]

線程池執行所送出的任務過程:

1、核心線程池中,所有線程都在執行任務嗎?

不是:建立一個線程執行剛送出的任務,

是:則進入第2步。

2、目前阻塞隊列滿了嗎?

沒滿:将送出的任務放在阻塞隊列中;

滿了,進入第3步。

3、線程池中所有線程都在執行任務嗎?

沒有:建立一個新的線程來執行任務,

有:交給飽和政策進行處理。

線程池執行器的分類

ThreadPoolExecutor(基礎線程池)

/**
 * 建立基礎線程池
 *
 * 線程池中數量沒有固定,可達到最大值( Interger MAX VALUE )
 * 線程池中的線程可進行緩存重複利用和回收(回收預設時間為1分鐘)
 * 當線程池中,沒有可用線程,會重新建立一個線程
 * 适合比較多的任務執行周期比較短的
 * ExecutorService executorService = Executors.newCachedThreadPool();
 *
 * 線程池中的線程處于一定的量,可以很好的控制線程的并發量
 * 超出一定的量被送出時需要在隊列中等待
 * ExecutorService executorService = Executors.newFixedThreadPool(5);
 *
 * 單線程線程池
 * 線程池中最多執行1個線程,之後送出的線程活動将會排在隊列中以此執行
 * 相當于第二個線程池中數量填寫1
 * ExecutorService executorService = Executors.newSingleThreadExecutor();
 *
 * ----------------------------------------
 *
 * 送出任務
 * executorService.execute(Runnable class);
 *
 * ----------------------------------------
 *
 * 關閉線程池
 * executorService.shutdown();
 *
 */
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i=0;i<20;i++){
	executorService.execute(new Task1());
}
executorService.shutdown();
           

ScheduledThreadPoolExecutor(可排程的線程池)

/**
 * 建立可排程的線程池
 *
 * 最多允許使用5個線程
 * 建立一個線程池 ,它可安排在給定延遲或定時後運作指令或者定期地執行。
 * 線程池中具有指定數量的線程,即便是空線程也将保留
 * ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
 *
 * ----------------------------------------
 *
 * 執行方法
 *
 * 每2個時間機關執行一次Task1,時間機關為秒
 * scheduledExecutorService.schedule(new Task1(),2, TimeUnit.SECONDS);
 *
 * 延遲1秒執行,每2秒執行一次,執行過程中不要關閉線程池.shutdown()
 * scheduledExecutorService.scheduleAtFixedRate(new Task1(),1,2, TimeUnit.SECONDS);
 *
 * ----------------------------------------
 * 
 * 關閉線程池
 * scheduledExecutorService.shutdown();
 * 
 */
 
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
scheduledExecutorService.schedule(new Task1(),2, TimeUnit.SECONDS);
scheduledExecutorService.shutdown();
           

ForkJoinPool

/**
 * JDK1.7之後的線程池,以分而治之的思想運作,簡單看看就行,太複雜了, 
 * 建立一個帶并行級别的線程池,并行級别決定了同一時刻最多有多少個線程在執行,如不傳如并行級别參數,将預設為目前系統的CPU個數
 * 企業中用的不多
 */

//建立線程池
ExecutorService executorService = Executors.newWorkStealingPool(4);
for(int i=0;i<10;i++){
	final int count = i;
	//送出任務
	executorService.submit(new Task1());
}
//放在這裡,防止程式提前跑完
while(true){}
           

線程池生命周期

/**
 * 線程池和線程的5種狀态不一樣
 * 線程池生命周期隻有兩種狀态
 * 除了兩種狀态之外,還有3種中間狀态
 *
 * RUNNING :能接受新送出的任務,并且也能處理阻塞隊列中的任務;
 *
 * SHUTDOWN:關閉狀态,不再接受新送出的任務,但卻可以繼續處理阻塞隊列中已儲存的任務。
 *
 * STOP:不能接受新任務,也不處理隊列中的任務,會中斷正在處理任務的線程。
 *
 * TIDYING:如果所有的任務都已終止了,workerCount (有效線程數) 為0,線程池進入該狀态後會調用 terminated() 方法進入TERMINATED 狀态。
 *
 * TERMINATED:在terminated() 方法執行完後進入該狀态,預設terminated()方法中什麼也沒有做
 */
           

參數含義

/**
 * 建立線程池源碼
 * 前4個參數可以自己調,後三個預設的就可以
 *
 * @param corePoolSize
 * 核心線程池的大小
 *
 * @param maximumPoolSize
 * 線程池能建立線程的最大個數
 *
 * @param keepAliveTime
 * 空閑線程存活時間
 *
 * @param unit
 * 時間機關,為keepAliveTime指定時間機關
 *
 * @param workQueue
 * 阻塞隊列,用于儲存任務的阻塞隊列
 *
 * @param defaultThreadFactory()
 * 建立線程的工程類
 *
 * @param defaultHandler         
 * 飽和政策(拒絕政策)
 */

public ThreadPoolExecutor(
		int corePoolSize, int maximumPoolSize,
		long keepAliveTime, TimeUnit unit,
		BlockingQueue<Runnable> workQueue
){
	this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}
           

阻塞隊列

/**
 * @param ArrayBlockingQueue(常用)
 * 基于數組的阻塞隊列實作
 * 在ArrayBlockingQueue内部,維護了一個定長數組,以便緩存隊列中的資料對象,
 * 并且内部還儲存着兩個整形變量,分别辨別着隊列的頭部和尾部在數組中的位置。
 * 而且生産者和消費者,都是共用同一個鎖對象,由此也意味着兩者無法真正并行。
 *
 * @param LinkedBlockingQueue(常用)
 * 基于連結清單的阻塞隊列,
 * 同ArrayListBlockingQueue類似,其内部也維持着一個資料緩沖隊列(該隊列由一個連結清單構成),
 * 生産者不停的生産資料,直到隊列已滿
 * 并且生産者端和消費者端分别采用了獨立的鎖來控制資料同步,
 * 是以在高并發的情況下生産者和消費者可以并行地操作隊列中的資料,提高整個隊列的并發性能。
 *
 * @param DelayQueue
 * 隻有當其指定的延遲時間到了,才能夠從隊列中擷取到該元素。
 * 這是一個沒有大小限制的隊列,是以往隊列中插入資料的操作(生産者)永遠不會被阻塞,
 * 而隻有擷取資料的操作(消費者)才會被阻塞。
 * DelayQueue使用場景較少,但都相當巧妙,
 * 常見的例子比如使用一個DelayQueue來管理一個逾時未響應的連接配接隊
 *
 * @param PriorityBlockingQueue
 * 基于優先級的阻塞隊列
 * 不會阻塞資料生産者,而隻會在沒有可消費的資料時,阻塞資料的消費者。
 * 是以要特别注意,生産者生産資料的速度絕對不能快于消費者消費資料的速度,
 * 否則時間一長,會最終耗盡所有的可用堆記憶體空間。
 *
 * @param SynchronousQueue
 * 一種無緩沖的等待隊列
 * 擁有公平模式和非公平模式
 */
           

常用兩種鎖的不同

/**
 * 1.隊列中鎖的實作不同
 *      ArrayBlockingQueue實作的隊列中的鎖是沒有分離的,即生産和消費用的是同一個鎖;
 *      LinkedBlockingQueue實作的隊列中的鎖是分離的 ,即生産用的是putLock,消費是takeLock
 *
 * 2.隊列大小初始化方式不同_
 *      ArrayBlockingQueue實作的隊列中必須指定隊列的大小,
 *      LinkedBlockingQueue實作的隊列中可以不指定隊列的大小,但是預設是Integer.MAX_VALUE
 */
           

拒絕政策

/**
 * @param ThreadPoolExecutor.AbortPolicy (最好用的)
 * 丢棄任務并抛出RejectedExecutionException異常。
 * 我們會知道線程池已滿,可以進行處理,例如增大線程池
 *
 * @param ThreadPoolExecutor.DiscardPolicy
 * 也是丢棄任務,但是不抛出異常。
 * 我們不知道線程池是否出什麼問題了,無法進行處理
 *
 * @param ThreadPoolExecutor.DiscardOldestPolicy
 * 丢棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
 * 萬一前面的任務很重要呢
 *
 * @param ThreadPoolExecutor.CallerRunsPolicy
 * 由調用線程處理該任務
 * 那隊列裡的任務怎麼辦,困死在裡面了
 */
           

execute方法執行邏輯

1. 如果目前運作的線程少于corePoolSize,則會建立新的線程來執行新的任務;
2. 如果運作的線程個數等于或者大于corePoolSize,則會将送出的任務存放到阻塞隊列workQueue中;
3. 如果目前workQueue隊列已滿的話,則會建立新的線程來執行任務;
4. 如果線程個數已經超過了maximumPoolSize,則會使用飽和政策RejectedExecutionHandler來進行處理。
           

線程池的關閉

原理:周遊線程池中的所有線程,然後依次中斷
1. shutdownNow首先将線程池的狀态設定為STOP,然後嘗試停止所有的正在執行和未執行任務的線程,并傳回等待執行任務的清單;
2. shutdown隻是将線程池的狀态設定為SHUTDOWN狀态,然後中斷所有沒有正在執行任務的線程