作爲面試的常住嘉賓之一,線程池的拷問,估計每個面試官都想問一遍。
下面,我們一起來學習一下。
一、線程池
首先,提到線程池就得說說它的好處,總得來說,可以分爲以下三點:
- 複用線程池的線程,避免線程創建和銷燬帶來的性能開銷。
- 控制線程池的最大併發數,避免大量線程之間搶佔系統資源而導致的阻塞現象
- 能夠對線程進行簡單的管理,並提供定時執行以及制定間隔循環執行等任務
但需要注意的一點,如果只有一個線程,且不需要複用,則不需要用到線程池,沒必要。
我們都知道,Java的線程池共有4中,newFixedThreadPool,newCachedThreadPool,newScheduledThreadPool,newSingleThreadExecutor 。但真正的線程池的實現爲 ThreadPoolExecutor ,它提供了一系列參數來配置線程池,上述4中,其實就是不同參數的 ThreadPoolExecutor 而已。
二、ThreadPoolExecutor
接着,我們看看 ThreadPoolExecutor 的構造參數:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
接着看看各個參數是什麼意思:
- corePoolSize:核心線程數
- maximumPoolSize:線程池的最大線程數
- keepAliveTime:非核心線程最大空閒存活時間(如果allowCoreThreadTimeOut = true,這個時間對核心線程也適用)
- unit:時間單位,有秒,分
- workQueue:線程池的任務等待隊列
- threadFactory :線程工程,在創建線程時,可以標準名字,方便調試
- handler:拒絕策略,當線程池無法處理任務時的拒絕方式
2.1 線程池的中線程的增加策略
那麼,從上面已經知道,線程的增加策略,跟三個參數有關:
- corPoolSize:核心線程數
- maximumPoolSize:線程池的最大線程數
-
- workQueue:線程池的任務等待隊列
他們之前的關係是這樣的,圖片來源
ThreadPoolExecutor 執行任務時,大致遵循以下規則:
- 如果線程池中的線程數據未達到核心線程的數量,那麼會直接啓動一個核心線程來執行任務
- 如果線程池中的線程已經達到了核心線程的數量,那麼任務會被插入到任務隊列中排隊,等待執行
- 如果隊列已滿,任務無法插入隊列中,如果此時線程數量未達到最大線程數,那麼會立刻啓動一個非核心線程來執行這個任務。
- 如果隊列已滿,且線程數已經達到最大線程數,則拒絕執行此任務,並調用 RejectedExecutionHandler 的 rejectedExecution 方法來調動調用發。
具體可以看下圖:圖片來源
這裏需要注意的點是,這個隊列需要是有界隊列的;否則線程數量就不會增加到最大線程數,因爲任務一直往隊列增加,而隊列一直未滿。
三、複用原理
上面理解了 ThreadPoolExecutor 的工作原理,那麼線程池的複用又是怎麼回事呢?
首先,當我們使用 ThreadPoolExecutor execute 一個任務時,看看它的源碼:
可以看到,這裏有個 addWorker 的方法,把 runnable 添加進去了,跟蹤進去:
看到,把 runnable 又傳遞給我了 Worker 這個實例,進去再看一下:
原來在 Worker 這裏新建了一個 thread,並任務賦值給 firstTask ,那麼這個 run 什麼時候調用呢,回到 addWorker 方法:
發現,最後執行了 worker 的 start 方法,那麼 worker 的run 方法也就執行了,你可以發現,其實你傳進來的runable 並沒有執行,而是執行 worker 自身的 runnable。
繼續看 run 方法中的 runWorker():
發現它裏面是一個 while 循環,當此時 task 爲空的時候,則使用 getTask() 去拿:
拿到 task 之後,就會執行 task.run ,這樣 runnable 就被複用了。
也可以這樣理解, task 的 thread 複用了任務的 runable ,thread 創建的那幾個,但是 runnable 可以多個,這樣就起到了複用的效果了。
四、線程池分類
上面介紹了 ThreadPoolExecutor 的使用和實現原理,下面介紹一下 Executors 中 4中常見的 線程池
4.1 FixedThreadPool
通過 Executors 的 newFixedThreadPool 創建,它是一種線程數固定的線程池,參數如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到,核心線程數和最大線程數是一直的,然後空閒時間爲0,隊列則是無線大。
從這裏看,線程一旦創建,就是核心線程,那麼空閒的時候,並不會回收,除非釋放;當任務超過核心線程,則放入到隊列中,等待執行,直到線程空閒出來。
4.2 CachedThreadPool
通過 Executors 的 newCachedThreadPool 創建,它是線程數量不固定的線程池,構造方法如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以看到,核心線程是0,最大線程數非常大,而時間設置爲60s,也就是說,60s內,如果線程空閒了,就會被回收。注意到的是,如果有任務來了,不會被立即執行,如果需要等待線程創建,並從隊列取出任務。它適合大量的耗時較少的任務,因爲當60s到來時,如果任務還未被執行,則會中斷任務。
4.3 ScheduledThreadPool
它的構造參數爲:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
看到,它的核心線程數是固定的,最大則是不固定的,適合做一些延時操作。
4.4 SingleThreadExecutor
它的構造參數爲:
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
核心線程數和最大線程數都爲1,確保所有任務都在這個線程中執行。
參考:
https://mp.weixin.qq.com/s/FOs7hiUk7_W2GoZsM1Tc_w