見招拆招:老油條教你如何化解大廠面試官的線程池奪命連環炮!!!
什么是線程池?
使用線程池的好處
線程池的核心參數
線程池的處理流程
線程池的創建方式有哪些?
常用線程池及它們的使用場景
線程池被創建后里面有線程嗎?
你知道有什么方法對線程池進行預熱嗎?
線程池的狀態有哪些?
線程池的拒絕策略有那些?
線程池的線程數到底怎么配置?
execute 和 submit的區別
尾言
什么是線程池?
使用線程池的好處
線程池的核心參數
線程池的處理流程
線程池的創建方式有哪些?
常用線程池及它們的使用場景
線程池被創建后里面有線程嗎?
你知道有什么方法對線程池進行預熱嗎?
線程池的狀態有哪些?
線程池的拒絕策略有那些?
線程池的線程數到底怎么配置?
execute 和 submit的區別
尾言
什么是線程池?
線程池可以理解為一個具有多個線程的線程集合.
使用線程池的好處
降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
線程池的核心參數
corePoolSize 核心線程數,沒達到核心線程數時,會創建新的線程。當達到核心線程數時,任務會進去隊列
maximumPoolSize 最大線程數,可以為Integer.MAX_VALUE 21億。當達到核心線程數且隊列滿了的時候,會去創建額外的線程來執行任務,最多不超過最大線程數
keepAliveTime 存活時間,當任務處理完成,額外的線程存活一段時間后,會自行銷毀。空閑等待時間(該參數默認對核心線程無效,當allowCoreThreadTimeOut手動設置為true時,核心線程超過存活時間后才會被銷毀)
TimeUnit 空閑等待時間的單位
BlockingQueue :任務進來,如果核心線程數滿了,則任務進入隊列中等待。
ThreadFactory 線程創建工廠
RejectExecutionHandler拒絕策略,當最大線程數滿了并且隊列也滿了的時候,如果再有任務進來就會啟用拒絕策略。
參考源碼
/** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters and default thread factory and rejected execution handler. * It may be more convenient to use one of the {@link Executors} factory * methods instead of this general purpose constructor. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @throws IllegalArgumentException if one of the following holds:
* {@code corePoolSize < 0}
* {@code keepAliveTime < 0}
* {@code maximumPoolSize <= 0}
* {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
線程池的處理流程
線程池的創建方式有哪些?
通過Executors工具類創建指定線程池
通過 new ThreadPoolExecutor() 自定義線程池,傳入指定參數
常用線程池及它們的使用場景
newFixedThreadPool():固定線程數的線程池
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
線程池特點:
核心線程數和最大線程數大小一樣
沒有所謂的非空閑時間,即keepAliveTime為0
阻塞隊列為無界隊列LinkedBlockingQueue
缺點
如果某任務執行時間過長,而導致大量任務堆積在阻塞隊列中,或者說在某一時刻大量任務進來則會導致機器內存使用不斷飆升,最終導致OOM
使用場景
newFixedThreadPool 適用于處理CPU密集型的任務,確保CPU在長期被工作線程使用的情況下,盡可能的少的分配線程,即適用執行長期的任務。
newCachedThreadPool()
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue
線程池特點:
核心線程數為0
最大線程數為Integer.MAX_VALUE
阻塞隊列是SynchronousQueue
非核心線程空閑存活時間為60秒
缺點
如果任務的提交速度大于線程處理任務的速度,那么就會不斷地創建新線程極端情況下會耗盡CPU和內存資源
CachedThreadPool允許創建的線程數量為 Integer.MAX_VALUE ,可能會創建大量線程,從而導致 OOM。
任務隊列采用的是SynchronousQueue,這個隊列是無法插入任務的,一有任務立即執行
使用場景
適用于并發執行大量短期的小任務。
newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
線程池特點
核心線程數為1
最大線程數也為1
阻塞隊列是LinkedBlockingQueue
keepAliveTime為0
缺點
LinkedBlockingQueue 為無界隊列,可能會導致OOM
使用場景
適用于串行執行任務的場景,一個任務一個任務地執行。
newScheduledThreadPool()
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
線程池特點
最大線程數為Integer.MAX_VALUE
阻塞隊列是DelayedWorkQueue
keepAliveTime為0
scheduleAtFixedRate() :按某種速率周期執行
scheduleWithFixedDelay():在某個延遲后執行
使用場景
周期性執行任務的場景,需要限制線程數量的場景
線程池被創建后里面有線程嗎?
線程池被創建后如果沒有任務過來,是不會有線程的。
你知道有什么方法對線程池進行預熱嗎?
==線程預熱可以使用以下兩個方法==
1.只啟動一個線程預熱
2.全部啟動預熱
線程池的狀態有哪些?
參考源碼
//記錄線程池的狀態,已經線程池中線程的個數,初始化狀態為 Running private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //線程池的五種狀態 private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS;
1、RUNNING
狀態說明:線程池處在RUNNING狀態時,能夠接收新任務,以及對已添加的任務進行處理。
狀態切換:線程池的初始化狀態是RUNNING。換句話說,線程池被一旦被創建,就處于RUNNING狀態,并且線程池中的任務數為0!
2、ShutDown
狀態說明:線程池處在SHUTDOWN狀態時,不接收新任務,但能處理已添加的任務。
狀態切換:調用線程池的shutdown() 時,線程池由RUNNING -> SHUTDOWN。
3、STOP
狀態說明:線程池處在STOP狀態時,不接收新任務,不處理已添加的任務,并且會中斷正在處理的任務。
狀態切換:調用線程池的shutdownNow() 時,線程池由(RUNNING or SHUTDOWN ) -> STOP。
4、tidying
狀態說明:當所有的任務已終止,ctl記錄的”任務數量”為0,線程池會變為TIDYING狀態。
當線程池變為TIDYING狀態時,會執行鉤子函數terminated()。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變為TIDYING時,進行相應的處理;可以通過重載terminated()函數來實現。
狀態切換:當線程池在SHUTDOWN狀態下,阻塞隊列為空并且線程池中執行的任務也為空時,就會由 SHUTDOWN -> TIDYING。當線程池在STOP狀態下,線程池中執行的任務為空時,就會由STOP -> TIDYING。
5、 TERMINATED(terminated)
狀態說明:線程池徹底終止,就變成TERMINATED狀態。
狀態切換:線程池處在TIDYING狀態時,執行完terminated()之后,就會由 TIDYING -> TERMINATED。
線程池的拒絕策略有那些?
AbortPolicy(默認),直接拋出一個類型為 RejectedExecutionException 的 RuntimeException異常阻止系統的正常運行。
DiscardPolicy:直接丟棄任務,不給予任何處理也不拋出異常。如果允許任務丟失的話,這是最好的方案。
DiscardOldestPolicy,拋棄隊列中等待時間最長的任務,然后把當前任務加入隊列中嘗試再次提交任務。
CallerRunsPolicy:"調用者運行"一種調節機制,該策略既不會拋棄任務也不會拋出異常,而是將某些任務回退到調用者,從而降低新任務的流量。
線程池的線程數到底怎么配置?
判斷當前任務是CPU 密集型還是 IO 密集型
公式
CPU 密集型任務(N+1): 這種任務消耗的主要是 CPU 資源,可以將線程數設置為 N(CPU 核心數)+1,比 CPU 核心數多出來的一個線程是為了防止線程偶發的缺頁中斷,或者其它原因導致的任務暫停而帶來的影響。 一旦任務暫停,CPU 就會處于空閑狀態,而在這種情況下多出來的一個線程就可以充分利用 CPU 的空閑時間。
I/O 密集型任務(2N): 這種任務應用起來,系統會用大部分的時間來處理 I/O 交互,而線程在處理 I/O 的時間段內不會占用 CPU 來處理,這時就可以將 CPU 交出給其它線程使用。因此在 I/O 密集型任務的應用中,我們可以多配置一些線程,具體的計算方法是 2N。
execute 和 submit的區別
1. 方法來源不同
execut()是在線程池的頂級接口Executor中定義的,而且只有這一個接口,可見這個方法的重要性。
public interface Executor { void execute(Runnable command); }
在ThreadPoolExecutor類中有它的具體實現。
submit()是在ExecutorService接口中定義的,并定義了三種重載方式,具體可以查看JDK文檔
2. 接受參數不同
execute()方法只能接收實現Runnable接口類型的任務
submit()方法則既可以接收Runnable類型的任務,也可以接收Callable類型的任務。
3. 返回值不同
execute()的返回值是void,線程提交后不能得到線程的返回值。
submit()的返回值是Future,通過Future的get()方法可以獲取到線程執行的返回值,get()方法是同步的,執行get()方法時,如果線程還沒執行完,會同步等待,直到線程執行完成。
雖然submit()方法可以提交Runnable類型的參數,但執行Future方法的get()時,線程執行完會返回null,不會有實際的返回值,這是因為Runable本來就沒有返回值
4. 對于異常處理不同
execute在執行任務時,如果遇到異常會直接拋出,
而submit不會直接拋出,只有在調用Future的get方法獲取返回值時,才會拋出異常。
尾言
我是 Code皮皮蝦,未來的日子里會不斷更新出對大家有益的博文,期待大家的關注!!!
創作不易,如果這篇博文對各位有幫助,希望各位小伙伴可以==和關注我哦==,感謝支持,我們下次再見~~~
==分享大綱==
大廠面試題專欄
Java從入門到入墳學習路線目錄索引
開源爬蟲實例教程目錄索引
更多精彩內容分享,請點擊 Hello World (●’?’●)
任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。