隊列
隊列是一種特殊的線性表先朦,特殊之處在于它只允許在表的前端(front)進行刪除操作,而在表的后端(rear)進行插入操作犬缨,和棧一樣喳魏,隊列是一種操作受限制的線性表。進行插入操作的端稱為隊尾怀薛,進行刪除操作的端稱為隊頭刺彩。
在隊列中插入一個隊列元素稱為入隊,從隊列中刪除一個隊列元素稱為出隊枝恋。因為隊列只允許在一端插入创倔,在另一端刪除,所以只有最早進入隊列的元素才能最先從隊列中刪除焚碌,故隊列又稱為先進先出(FIFO—first in first out)線性表畦攘。
阻塞隊列
- 支持阻塞的插入方法:意思是當隊列滿時,隊列會阻塞插入元素的線程呐能,直到隊列不滿念搬。
- 支持阻塞的移除方法:意思是在隊列為空時,獲取元素的線程會等待隊列變?yōu)榉强铡?/li>
方法 | 拋出異常 | 返回特殊值 | 一直阻塞 | 超時退出 |
---|---|---|---|---|
插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除方法 | remove | poll() | take() | poll(time,unit) |
檢查方法 | element() | peek() | 不可用 | 不可用 |
- 拋出異常:當隊列滿時摆出,如果再往隊列里插入元素朗徊,會拋出IllegalStateException("Queuefull")異常。當隊列空時偎漫,從隊列里獲取元素會拋出NoSuchElementException異常爷恳。
- 返回特殊值:當往隊列插入元素時,會返回元素是否插入成功象踊,成功返回true温亲。如果是移除方法棚壁,則是從隊列里取出一個元素,如果沒有則返回null栈虚。
- 一直阻塞:當阻塞隊列滿時袖外,如果生產(chǎn)者線程往隊列里put元素,隊列會一直阻塞生產(chǎn)者線程魂务,直到隊列可用或者響應中斷退出曼验。當隊列空時,如果消費者線程從隊列里take元素粘姜,隊列會阻塞住消費者線程鬓照,直到隊列不為空。
- 超時退出:當阻塞隊列滿時孤紧,如果生產(chǎn)者線程往隊列里插入元素豺裆,隊列會阻塞生產(chǎn)者線程一段時間,如果超過了指定的時間号显,生產(chǎn)者線程就會退出臭猜。
常用的阻塞隊列
- ArrayBlockingQueue:一個由數(shù)組結構組成的有界阻塞隊列
- LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列
- PriorityBlockingQueue:一個支持優(yōu)先級排序的無界阻塞隊列
正常按照放入順序獲取,優(yōu)先級(放入的時候根據(jù)某種方式比較咙轩,按照比較規(guī)則排序) - DelayQueue:一個使用優(yōu)先級隊列實現(xiàn)的無界阻塞隊列
元素的延遲獲取,即使有元素,元素的剩余時間沒到,也取不到(例如單機緩存系統(tǒng)) - SynchronousQueue:一個不存儲元素的阻塞隊列
不存儲元素,必須有一個消費者在調(diào)用take在進行取出操作 ,直接取出,為了解耦 - LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列
- transfer
添加元素的時候 如果有消費者在接收 直接傳給消費者 如果沒有阻塞住 等有消費者拿元素
tryTransfer - 添加元素的時候 如果有消費者在接收 直接傳給消費者 如果沒有添加到隊列中
- transfer
- LinkedBlockingQueues:一個由鏈表結構組成的雙向阻塞隊列
一般隊列 一個入口一個出口 隊列兩端都可以作為入口 也可以作為出口 兩端可以同時操作 減少競爭
線程池
為什么要用線程池获讳?
- 降低資源消耗。通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗活喊。
- 提高響應速度丐膝。當任務到達時,任務可以不需要等到線程創(chuàng)建就能立即執(zhí)行钾菊。假設一個服務器完成一項任務所需時間為:T1 創(chuàng)建線程時間帅矗,T2 在線程中執(zhí)行任務的時間,T3 銷毀線程時間煞烫。 如果:T1 + T3 遠大于 T2浑此,則可以采用線程池,以提高服務器性能滞详。線程池技術正是關注如何縮短或調(diào)整T1,T3時間的技術凛俱,從而提高服務器程序性能的。它把T1料饥,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段蒲犬,這樣在服務器程序處理客戶請求時,不會有T1岸啡,T3的開銷了原叮。
- 提高線程的可管理性。線程是稀缺資源,如果無限制地創(chuàng)建奋隶,不僅會消耗系統(tǒng)資源擂送,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一分配唯欣、調(diào)優(yōu)和監(jiān)控嘹吨。
ThreadPoolExecutor 的類關系
Executor是一個接口,它是Executor框架的基礎黍聂,它將任務的提交與任務的執(zhí)行分離開來躺苦。
ExecutorService接口繼承了Executor身腻,在其上做了一些shutdown()产还、submit()的擴展,可以說是真正的線程池接口嘀趟;
AbstractExecutorService抽象類實現(xiàn)了ExecutorService接口中的大部分方法脐区;
ThreadPoolExecutor是線程池的核心實現(xiàn)類,用來執(zhí)行被提交的任務她按。
ScheduledExecutorService接口繼承了ExecutorService接口牛隅,提供了帶"周期執(zhí)行"功能ExecutorService;
ScheduledThreadPoolExecutor是一個實現(xiàn)類酌泰,可以在給定的延遲后運行命令媒佣,或者定期執(zhí)行命令。ScheduledThreadPoolExecutor比Timer更靈活陵刹,功能更強大默伍。
線程池的創(chuàng)建各個參數(shù)含義
/**
* corePoolSize 核心線程數(shù)
* maximumPoolSize 最大線程數(shù)
* keepAliveTime 空閑線程存活時間
* unit 存活時間的時間單位
* workQueue 任務過多時 存儲任務用
* threadFactory
* handler 拒絕策略 任務數(shù)過多 超過(最大線程數(shù)+任務阻塞隊列的和 )時對任務的處理
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//省略...
}
- corePoolSize
線程池中的核心線程數(shù),當提交一個任務時衰琐,線程池創(chuàng)建一個新線程執(zhí)行任務也糊,直到當前線程數(shù)等于corePoolSize;
如果當前線程數(shù)為corePoolSize羡宙,繼續(xù)提交的任務被保存到阻塞隊列中狸剃,等待被執(zhí)行;
如果執(zhí)行了線程池的prestartAllCoreThreads()方法狗热,線程池會提前創(chuàng)建并啟動所有核心線程钞馁。 - maximumPoolSize
線程池中允許的最大線程數(shù)。如果當前阻塞隊列滿了匿刮,且繼續(xù)提交任務僧凰,則創(chuàng)建新的線程執(zhí)行任務,前提是當前線程數(shù)小于maximumPoolSize - keepAliveTime
線程空閑時的存活時間僻焚,即當線程沒有任務執(zhí)行時允悦,繼續(xù)存活的時間。默認情況下,該參數(shù)只在線程數(shù)大于corePoolSize時才有用 - TimeUnit
keepAliveTime的時間單位 - workQueue
workQueue必須是BlockingQueue阻塞隊列隙弛。當線程池中的線程數(shù)超過它的corePoolSize的時候架馋,線程會進入阻塞隊列進行阻塞等待。通過workQueue全闷,線程池實現(xiàn)了阻塞功能叉寂。
一般來說,我們應該盡量使用有界隊列总珠,因為使用無界隊列作為工作隊列會對線程池帶來如下影響屏鳍。
1)當線程池中的線程數(shù)達到corePoolSize后,新任務將在無界隊列中等待局服,因此線程池中的線程數(shù)不會超過corePoolSize钓瞭。
2)由于1,使用無界隊列時maximumPoolSize將是一個無效參數(shù)淫奔。
3)由于1和2山涡,使用無界隊列時keepAliveTime將是一個無效參數(shù)。
4)更重要的唆迁,使用無界queue可能會耗盡系統(tǒng)資源鸭丛,有界隊列則有助于防止資源耗盡,同時即使使用有界隊列唐责,也要盡量控制隊列的大小在一個合適的范圍鳞溉。 - threadFactory
創(chuàng)建線程的工廠,通過自定義的線程工廠可以給每個新建的線程設置一個具有識別度的線程名鼠哥,當然還可以更加自由的對線程做更多的設置熟菲,比如設置所有的線程為守護線程。
Executors靜態(tài)工廠里默認的threadFactory肴盏,線程的命名規(guī)則是“pool-數(shù)字-thread-數(shù)字”科盛。 - RejectedExecutionHandler
線程池的飽和策略,當阻塞隊列滿了菜皂,且沒有空閑的工作線程贞绵,如果繼續(xù)提交任務,必須采取一種策略處理該任務恍飘,線程池提供了4種策略:
(1)AbortPolicy:直接拋出異常榨崩,默認策略;
(2)CallerRunsPolicy:用調(diào)用者所在的線程來執(zhí)行任務章母;
(3)DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務母蛛,并執(zhí)行當前任務;
(4)DiscardPolicy:直接丟棄任務乳怎;
當然也可以根據(jù)應用場景實現(xiàn)RejectedExecutionHandler接口彩郊,自定義飽和策略,如記錄日志或持久化存儲不能處理的任務。
線程池的工作機制
- 如果當前運行的線程少于corePoolSize秫逝,則創(chuàng)建新線程來執(zhí)行任務(注意恕出,執(zhí)行這一步驟需要獲取全局鎖)。
- 如果運行的線程等于或多于corePoolSize违帆,則將任務加入BlockingQueue浙巫。
- 如果無法將任務加入BlockingQueue(隊列已滿),則創(chuàng)建新的線程來處理任務刷后。
- 如果創(chuàng)建新線程將使當前運行的線程超出maximumPoolSize的畴,任務將被拒絕,并調(diào)用RejectedExecutionHandler.rejectedExecution()方法尝胆。
提交任務
execute()方法用于提交不需要返回值的任務丧裁,所以無法判斷任務是否被線程池執(zhí)行成功。
submit()方法用于提交需要返回值的任務班巩。線程池會返回一個future類型的對象渣慕,通過這個future對象可以判斷任務是否執(zhí)行成功,并且可以通過future的get()方法來獲取返回值抱慌,get()方法會阻塞當前線程直到任務完成,而使用get(long timeout眨猎,TimeUnit unit)方法則會阻塞當前線程一段時間后立即返回抑进,這時候有可能任務沒有執(zhí)行完。
關閉線程池
可以通過調(diào)用線程池的shutdown或shutdownNow方法來關閉線程池睡陪。
它們的原理是遍歷線程池中的工作線程寺渗,然后逐個調(diào)用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止兰迫。
但是它們存在一定的區(qū)別信殊,shutdownNow首先將線程池的狀態(tài)設置成STOP,然后嘗試停止所有的正在執(zhí)行或暫停任務的線程汁果,并返回等待執(zhí)行任務的列表涡拘。
shutdown只是將線程池的狀態(tài)設置成SHUTDOWN狀態(tài),然后中斷所有沒有正在執(zhí)行任務的線程据德。
只要調(diào)用了這兩個關閉方法中的任意一個鳄乏,isShutdown方法就會返回true。
當所有的任務都已關閉后棘利,才表示線程池關閉成功橱野,這時調(diào)用isTerminaed方法會返回true。
至于應該調(diào)用哪一種方法來關閉線程池善玫,應該由提交到線程池的任務特性決定水援,通常調(diào)用shutdown方法來關閉線程池,如果任務不一定要執(zhí)行完,則可以調(diào)用shutdownNow方法蜗元。
如何合理的配置參數(shù)
- 線程大小
cpu密集型(cpu在不停計算的誓斥,從內(nèi)存中取數(shù)據(jù)計算的)
maximumPoolSize 不要超過cpu核心數(shù) 避免cpu切換
最多 cpu核心數(shù)+1 (Runtime.getRuntime().availableProcessors();可以獲取CPU核心數(shù))
為什么+1? 機器內(nèi)存是有限的,會把磁盤的一部分作為虛擬內(nèi)存许帐,訪問這部分數(shù)據(jù)的時候會先將數(shù)據(jù)拷貝到內(nèi)存中劳坑,拷貝步驟是耗時的,會造成cpu空閑成畦,產(chǎn)生頁缺失現(xiàn)象距芬。
所以這時+1 避免這種情況的產(chǎn)生 (頁缺失操作系統(tǒng)的概念,也不是很清楚)io密集型(網(wǎng)絡通訊循帐、磁盤操作等框仔,遠遠低于內(nèi)存的存取速度 )
io操作基本不用cpu,cpu告訴磁盤控制器(DMA )去做拄养,做完告訴cpu
根據(jù)日常開發(fā)得到的經(jīng)驗值 cpu核心數(shù)2避免等待讀取數(shù)據(jù) 造成cpu空閑混合型(上面兩者都有的)
如果兩種任務時間相差不大 考慮拆分
如果相差較大 cpu10ms 磁盤5s 直接視為io型
- 阻塞隊列的大小
盡量配置成有界的离斩,如果當時我們設置成無界隊列,那么線程池的隊列就會越來越多瘪匿,有可能會撐滿內(nèi)存
線程池的線程是怎么做到不銷毀的跛梗?
從阻塞隊列中獲取任務 run方法執(zhí)行不完