前言 在我們平時自己寫線程的測試demo時,一般都是用new Thread的方式來創建線程。但是,我們知道創建線程對象,就會在記憶體中開闢空間,而線程中的任務執行完畢之後,就會銷毀。 單個線程的話還好,如果線程的併發數量上來之後,就會頻繁的創建和銷毀對象。這樣,勢必會消耗大量的系統資源,進而影響執行效 ...
前言
在我們平時自己寫線程的測試demo時,一般都是用new Thread的方式來創建線程。但是,我們知道創建線程對象,就會在記憶體中開闢空間,而線程中的任務執行完畢之後,就會銷毀。
單個線程的話還好,如果線程的併發數量上來之後,就會頻繁的創建和銷毀對象。這樣,勢必會消耗大量的系統資源,進而影響執行效率。
所以,線程池就應運而生。
線程池ThreadPoolExecutor
可以通過idea先看下線程池的類圖,瞭解一下它的繼承關係和大概結構。
它繼承自AbstractExecutorService類,這是一個抽象類,不過裡邊的方法都是已經實現好的。然後這個類實現了ExecutorService介面,裡邊聲明瞭各種方法,包括關閉線程池,以及線程池是否已經終止等。此介面繼承自父介面Executor,裡邊只聲明瞭一個execute方法。
線程池就是為瞭解決單個線程頻繁的創建和銷毀帶來的性能開銷。同時,可以幫我們自動管理線程。並且不需要每次執行新任務都去創建新的線程,而是重覆利用已有的線程,大大提高任務執行效率。
我們打開 ThreadPoolExecutor的源碼,可以看到總共有四個構造函數。
但是,前三個最終都會調用到最後一個構造函數。我們來看下這個構造函數都有哪些參數。(其實,多看下參數的英文解釋就能明白其中的含義,看來英語對程式員來說是真的重要呀)
//核心構造函數
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
1)corePoolSize
代表核心線程數。每當新的任務提交過來的時候,線程池就會創建一個核心線程來執行這個任務,即使已經有其他的核心線程處於空閑狀態。 而當需要執行的任務數大於核心線程數時,將不再創建新的核心線程。
其實,我們可以看下JDK提供的官方註釋說明。even if they are idle,就照應上邊的加粗字體。
此外,最後一句話說,除非allowCoreThreadTimeOut 這個參數被設置了值。
什麼意思呢,可以去看下這個參數預設值是false,代表當核心線程在空閑狀態時,即沒有任務在執行,就會一直存活,不會銷毀。而設置為true之後,就會有線程存活時間,即假如設置存活時間60秒,則60秒之後,如果沒有新的可執行任務,則核心線程也會自動銷毀。
2)maximumPoolSize
線程所允許的最大數量。即,當阻塞隊列已滿的時候,並且已經創建的線程數小於最大線程數,則會創建新的線程去執行任務。所以,這個參數只有在阻塞隊列滿的情況下才有意義。因此,對於無界隊列,這個參數將會失去效果。
3)keepAliveTime
代表線程空閑後,保持存活的時間。也就是說,超過一定的時間沒有任務執行,線程就會自動銷毀。
註意,這個參數,是針對大於核心線程數,小於最大線程數的那部分非核心線程來說的。如果是任務數量特別多的情況下,可以適當增加這個參數值的大小。以保證,在下個任務到來之前,此線程不會立即銷毀,從而避免線程的重新創建。
4)unit
這個是描述存活時間的時間單位。可以使用TimeUnit裡邊的枚舉值。
5)workQueue
代表阻塞隊列,存儲所有等待執行的任務。
6)threadFactory
代表用來創建線程的工廠。可以自定義一個工廠,傳參進來。如果不指定的話,就會使用預設工廠(Executors類裡邊的 DefaultThreadFactory)。
可以看到,會給每個線程的名字指定一個有規律的首碼。並且每個線程都設置相同的優先順序(優先順序總共有三個,1、5、10)。優先順序可以理解為,優先順序高的線程被執行的概率會更高,但是不代表優先順序高的線程一定會先執行。
7)handler
這個參數代表,拒絕策略。當阻塞隊列和線程池都滿了,即達到了最大線程數,會用什麼策略來處理。一共有四種策略可供選擇,分別對應四個內部類。
- AbortPolicy:直接拒絕,並拋出異常,這也是預設的策略。
- CallerRunsPolicy:直接讓調用execute方法的線程去執行此任務。
- DiscardOldestPolicy:丟棄最老的未處理的任務,然後重新嘗試執行當前的新任務。
- DiscardPolicy:直接丟棄當前任務,但是不拋異常。
總結一下線程池的執行過程。
- 當線程數量未達到corePoolSize的時候,就會創建新的線程來執行任務。
- 當核心線程數已滿,就會把任務放到阻塞隊列。
- 當隊列已滿,並且未達到最大線程數,就會新建非核心線程來執行任務。
- 當隊列已滿,並且達到了最大線程數,則選擇一種拒絕策略來執行。
線程池常用的一些方法
我們一般用 execute 方法來提交任務給線程池。當線程需要返回值時,可以使用submit 方法。
shutdown方法用來關閉線程池。註意,此時不再接受新提交的任務,但是,會繼續處理正在運行的任務和阻塞隊列裡邊的任務。
shutdownNow也會關閉線程池。但是,它不再接受新任務,並且會嘗試終止正在運行的任務。
用Executors創建線程池
瞭解了線程池工作流程之後,那麼我們怎樣去創建它呢。
Executors類提供了四種常用的方法。可以發現它們最終都調用了線程池的構造方法。都有兩種創建方式,其中一種可以傳自定義的線程工廠。此處,只貼出不帶工廠的方法便於理解。
①newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
創建一個固定大小的線程池。核心線程數和最大線程數相等。當線程數量達到核心線程數時,新任務就會放到阻塞隊列裡邊等待執行。
②newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
創建一個核心線程數和最大線程數都是1的線程池。即線程池中只會存在一個正在執行的線程,若線程空閑則執行,否則把任務放到阻塞隊列。
③ newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
創建一個可根據實際情況調整線程個數的線程池。這句話,可以理解為,有多少任務同時進來,就會創建同等數量的線程去執行任務。當然,這是線上程數不能超過Integer最大值的前提下。
當再來一個新任務時,若有空閑線程則執行任務。否則,等線程空閑60秒之後,就會自動回收。
當沒有新任務,就不會創建新的線程。
④newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
創建一個可指定核心線程數的線程池。這個線程池可以執行周期性的任務。
如果本文對你有用,歡迎點贊,評論,轉發。
學習是枯燥的,也是有趣的。我是「煙雨星空」,歡迎關註,可第一時間接收文章推送。