目錄
引出
摘自阿里巴巴開發手冊:
【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣 的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。 說明:Executors 返回的線程池對象的弊端如下:
- 1)FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
- 2)CachedThreadPool 和 ScheduledThreadPool: 允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。
線程池創建的參數
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:線程池始終線程數,即使有些是空閒的。設置
allowCoreThreadTimeOut
參數爲true,纔會進行回收。 - maximumPoolSize:線程池最大線程數,表示在線程池中最多能創建多少個線程。如果當線程池中的數量到達這個數字時,新來的任務會拋出異常。
- keepAliveTime:表示線程沒有任務執行時最多能保持多少時間會回收,然後線程池的數目維持在corePoolSize。
- unit:參數keepAliveTime的時間單位
- workQueue:一個阻塞隊列,所有的任務都會先放在這裏,務;如果對當前對線程的需求超過了corePoolSize大小,會用來存儲等待執行的任。
- threadFactory:線程工廠,主要用來創建線程,比如指定線程的名字。
- handler:如果線程池已滿,新的任務處理方式。
注意一點:初始化線程池時,線程數爲0
測試
測試點
- coreSize = 1,MaxSize=2,阻塞隊列爲1,如果提交三個任務會怎麼樣?
- coreSize = 1,MaxSize=2,阻塞隊列大小爲5,如果提交七個任務會怎麼樣?
- coreSize = 1,MaxSize=2,阻塞隊列大小爲5,如果提交八個任務會怎麼樣?
測試代碼
public class ThreadPoolExecutorBuild {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = bulidThreadPoolExecutor();
int activeCount = -1;
int queueSize = -1;
while (true){
if (activeCount!=threadPoolExecutor.getActiveCount() || queueSize !=threadPoolExecutor.getQueue().size()){
activeCount = threadPoolExecutor.getActiveCount();
queueSize = threadPoolExecutor.getQueue().size();
System.out.println("活躍的線程數:"+threadPoolExecutor.getActiveCount());
System.out.println("getCorePoolSize: "+threadPoolExecutor.getCorePoolSize());
System.out.println("阻塞隊列的任務數:"+threadPoolExecutor.getQueue().size());
System.out.println("最大的線程數:"+threadPoolExecutor.getMaximumPoolSize());
System.out.println("======================");
}
}
}
private static ThreadPoolExecutor bulidThreadPoolExecutor() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,
30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(1), Thread::new,new ThreadPoolExecutor.AbortPolicy());
System.out.println("The thread pool creat done.");
threadPoolExecutor.execute(()->sleepSeconds(100));
threadPoolExecutor.execute(()->sleepSeconds(100));
threadPoolExecutor.execute(()->sleepSeconds(100));
return threadPoolExecutor;
}
private static void sleepSeconds(long sec){
try {
System.out.println(" >>> " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(sec);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結果
測試一結果:
The thread pool creat done.
>>> Thread-0
>>> Thread-1
活躍的線程數:2
getCorePoolSize: 1
阻塞隊列的任務數:1
最大的線程數:2
======================
測試二結果
The thread pool creat done.
>>> Thread-0
>>> Thread-1
活躍的線程數:2
getCorePoolSize: 1
阻塞隊列的任務數:5
最大的線程數:2
======================
測試三結果
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task ThreadPool.ThreadPoolExecutorBuild$$Lambda$9/2065951873@6acbcfc0 rejected from java.util.concurrent.ThreadPoolExecutor@5f184fc6[Running, pool size = 2, active threads = 2, queued tasks = 5, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at ThreadPool.ThreadPoolExecutorBuild.bulidThreadPoolExecutor(ThreadPoolExecutorBuild.java:44)
at ThreadPool.ThreadPoolExecutorBuild.main(ThreadPoolExecutorBuild.java:11)
結論:
- 當阻塞隊列滿時,纔會創建新的線程
- 當線程有空閒時,超過一定時間會被回收
- 當線程數達到最大值,且阻塞隊列滿時,會執行拒絕策略
- 核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
線程池的阻塞隊列的選擇?
1. ArrayBlockingQueue
2. LinkedBlockingQueue
3. PriorityBlockingQueue
4. SynchronousQueue
- ArrayBlockingQueue:是一個有邊界的阻塞隊列,它的內部實現是一個數組。
它的容量在初始化時就確定不變。
- LinkedBlockingQueue:阻塞隊列
大小的配置是可選的
,其內部實現是一個鏈表。 - PriorityBlockingQueue:是一個沒有邊界的隊列,所有插入到PriorityBlockingQueue的對象必須實現java.lang.Comparable接口,隊
列優先級的排序就是按照我們對這個接口的實現來定義的。
- SynchronousQueue:隊列內部
僅允許容納一個元素
。當一個線程插入一個元素後會被阻塞,除非這個元素被另一個線程消費。
workQueue常用的是:java.util.concurrent.ArrayBlockingQueue
拒絕策略
handler有四個選擇:
- ThreadPoolExecutor.AbortPolicy(): 拋出java.util.concurrent.RejectedExecutionException異常
- ThreadPoolExecutor.CallerRunsPolicy():重試添加當前的任務,他會自動重複調用execute()方法
- ThreadPoolExecutor.DiscardOldestPolicy():拋棄舊的任務
- ThreadPoolExecutor.DiscardPolicy(): 拋棄當前的任務
關閉
關閉有兩個方法:shutdown
和shutdownNow
shutdown
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
- 從源碼可以看出,本質上執行的是
interrupt
方法 - 如果線程是空閒的,執行的是Condition的await的方法,會被直接打斷,被回收
- 如果正在工作,該線程會被打上一個標記,等任務執行後被回收
shutdownNow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();//先打斷
tasks = drainQueue();//再把任務隊列沒有執行的任務取出
} finally {
mainLock.unlock();
}
tryTerminate();//不斷的打斷
return tasks;
}
- 先打斷空閒的打斷
- 然後清空任務隊列
- 然後不斷的嘗試打斷正在執行的線程
- 最後會返回一個List集合,包含還沒有執行的任務
守護線程
如果有些任務執行時間很長,想要關閉,可以利用守護線程的方式強制關閉。
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,
30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5), r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
},new ThreadPoolExecutor.AbortPolicy());
被守護的線程是主線程,只要主線程執行完成,線程池就會強制關閉,可以配合awaitTermination
方法使用
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = bulidThreadPoolExecutor();
threadPoolExecutor.shutdown();
threadPoolExecutor.awaitTermination(5,TimeUnit.SECONDS);
System.out.println("強制關閉");
}
一個關閉的陷阱
- 當我們關閉線程時,如果一個線程正在執行,被打斷後,也會被系統認爲完成了任務
- 當使用
shutdownNow
時,不會返回到集合中,此時該任務就莫名的消失了
API
關閉核心線程
public void allowCoreThreadTimeOut(boolean value)
- 當賦值爲true時,核心線程在空閒的時候也會被銷燬
更改線程池核心參數
public void setCorePoolSize(int corePoolSize)
public void setMaximumPoolSize(int maximumPoolSize)
public void setKeepAliveTime(long time,
TimeUnit unit)
public void setThreadFactory(ThreadFactory threadFactory)
public void setRejectedExecutionHandler(RejectedExecutionHandler handler)
除了阻塞隊列,可以看出線程池參數都是可以更改的
創建初始線程
public int prestartAllCoreThreads()
默認線程數是0,使用後,會初始化到核心線程數
測試
public class ExecutorServiceTest {
public static void main(String[] args) {
ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
executorService.prestartAllCoreThreads();
int poolSize = executorService.getPoolSize();
System.out.println(poolSize);
}
}
結果
5
清空阻塞隊列
public void purge()
- 所有沒有執行的任務會被清空