隊(duì)列
隊(duì)列绎橘,又稱為佇列(queue),是先進(jìn)先出(FIFO, First-In-First-Out)的線性表。在具體應(yīng)用中通常用鏈表或者數(shù)組來(lái)實(shí)現(xiàn)。隊(duì)列只允許在后端(稱為rear)進(jìn)行插入操作谈竿,在前端(稱為front)進(jìn)行刪除操作。
隊(duì)列的操作方式和堆棧類似摸吠,唯一的區(qū)別在于隊(duì)列只允許新數(shù)據(jù)在后端進(jìn)行添加空凸。
線程池
-
隊(duì)列是一種特殊的線性表,特殊之處在于它只允許在表的前端(front)進(jìn)行刪除操作蜕便,而在表的后端(rear)進(jìn)行插入操作劫恒,和棧一樣,隊(duì)列是一種操作受限制的線性表轿腺。進(jìn)行插入操作的端稱為隊(duì)尾两嘴,進(jìn)行刪除操作的端稱為隊(duì)頭。
在隊(duì)列中插入一個(gè)隊(duì)列元素稱為入隊(duì)族壳,從隊(duì)列中刪除一個(gè)隊(duì)列元素稱為出隊(duì)憔辫。因?yàn)殛?duì)列只允許在一端插入,在另一端刪除仿荆,所以只有最早進(jìn)入隊(duì)列的元素才能最先從隊(duì)列中刪除贰您,故隊(duì)列又稱為先進(jìn)先出(FIFO—first in first out)線性表。
什么是阻塞隊(duì)列
1)支持阻塞的插入方法:意思是當(dāng)隊(duì)列滿時(shí)拢操,隊(duì)列會(huì)阻塞插入元素的線程锦亦,直到隊(duì)列補(bǔ)滿。
2)支持阻塞的移除方法:意思是在隊(duì)列為空時(shí)令境,獲取元素的線程會(huì)等待隊(duì)列變?yōu)榉强铡?/h4>
在并發(fā)編程中使用生產(chǎn)者和消費(fèi)者模式能夠解決絕大多數(shù)并發(fā)問(wèn)題杠园。該模式通過(guò)平衡生產(chǎn)線程和消費(fèi)線程的工作能力來(lái)提高程序整體處理數(shù)據(jù)的速度。
在線程世界里舔庶,生產(chǎn)者就是生產(chǎn)數(shù)據(jù)的線程抛蚁,消費(fèi)者就是消費(fèi)數(shù)據(jù)的線程。在多線程開發(fā)中惕橙,如果生產(chǎn)者處理速度很快瞧甩,而消費(fèi)者處理速度很慢,那么生產(chǎn)者就必須等待消費(fèi)者處理完弥鹦,才能繼續(xù)生產(chǎn)數(shù)據(jù)肚逸。同樣的道理,如果消費(fèi)者的處理能力大于生產(chǎn)者,那么消費(fèi)者就必須等待生產(chǎn)者朦促。
為了解決這種生產(chǎn)消費(fèi)能力不均衡的問(wèn)題犬钢,便有了生產(chǎn)者和消費(fèi)者模式。生產(chǎn)者和消費(fèi)者模式是通過(guò)一個(gè)容器來(lái)解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問(wèn)題思灰。生產(chǎn)者和消費(fèi)者彼此之間不直接通信,而是通過(guò)阻塞隊(duì)列來(lái)進(jìn)行通信混滔,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費(fèi)者處理洒疚,直接扔給阻塞隊(duì)列,消費(fèi)者不找生產(chǎn)者要數(shù)據(jù)坯屿,而是直接從阻塞隊(duì)列里取油湖,阻塞隊(duì)列就相當(dāng)于一個(gè)緩沖區(qū),平衡了生產(chǎn)者和消費(fèi)者的處理能力领跛。
-
阻塞隊(duì)列常用于生產(chǎn)者和消費(fèi)者的場(chǎng)景乏德,生產(chǎn)者是向隊(duì)列里添加元素的線程,消費(fèi)者是從隊(duì)列里取元素的線程吠昭。阻塞隊(duì)列就是生產(chǎn)者用來(lái)存放元素喊括、消費(fèi)者用來(lái)獲取元素的容器。
拋出異常:當(dāng)隊(duì)列滿時(shí)矢棚,如果再往隊(duì)列里插入元素郑什,會(huì)拋出IllegalStateException("Queuefull")異常。當(dāng)隊(duì)列空時(shí)蒲肋,從隊(duì)列里獲取元素會(huì)拋出NoSuchElementException異常蘑拯。
返回特殊值:當(dāng)往隊(duì)列插入元素時(shí),會(huì)返回元素是否插入成功兜粘,成功返回true申窘。如果是移除方法,則是從隊(duì)列里取出一個(gè)元素孔轴,如果沒(méi)有則返回null剃法。
一直阻塞:當(dāng)阻塞隊(duì)列滿時(shí),如果生產(chǎn)者線程往隊(duì)列里put元素距糖,隊(duì)列會(huì)一直阻塞生產(chǎn)者線程玄窝,直到隊(duì)列可用或者響應(yīng)中斷退出。當(dāng)隊(duì)列空時(shí)悍引,如果消費(fèi)者線程從隊(duì)列里take元素恩脂,隊(duì)列會(huì)阻塞住消費(fèi)者線程,直到隊(duì)列不為空趣斤。
超時(shí)退出:當(dāng)阻塞隊(duì)列滿時(shí)俩块,如果生產(chǎn)者線程往隊(duì)列里插入元素,隊(duì)列會(huì)阻塞生產(chǎn)者線程一段時(shí)間,如果超過(guò)了指定的時(shí)間玉凯,生產(chǎn)者線程就會(huì)退出势腮。
常用阻塞隊(duì)列,其實(shí)在源碼里面多次使用到 ReentrantLock 漫仆,顯示鎖捎拯,關(guān)系到了AQS原理
·ArrayBlockingQueue:一個(gè)由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列。
·LinkedBlockingQueue:一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列盲厌。
·PriorityBlockingQueue:一個(gè)支持優(yōu)先級(jí)排序的無(wú)界阻塞隊(duì)列署照。
·DelayQueue:一個(gè)使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無(wú)界阻塞隊(duì)列。
·SynchronousQueue:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列吗浩。
·LinkedTransferQueue:一個(gè)由鏈表結(jié)構(gòu)組成的無(wú)界阻塞隊(duì)列建芙。
·LinkedBlockingDeque:一個(gè)由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。
無(wú)界和有界
有限隊(duì)列就是長(zhǎng)度有限懂扼,滿了以后生產(chǎn)者會(huì)阻塞禁荸,無(wú)界隊(duì)列就是里面能放無(wú)數(shù)的東西而不會(huì)因?yàn)殛?duì)列長(zhǎng)度限制被阻塞,當(dāng)然空間限制來(lái)源于系統(tǒng)資源的限制阀湿,如果處理不及時(shí)赶熟,導(dǎo)致隊(duì)列越來(lái)越大越來(lái)越大,超出一定的限制致使內(nèi)存超限炕倘,操作系統(tǒng)或者JVM幫你解決煩惱钧大,直接把你 OOM kill 省事了。
無(wú)界也會(huì)阻塞罩旋,為何啊央?因?yàn)樽枞粌H僅體現(xiàn)在生產(chǎn)者放入元素時(shí)會(huì)阻塞,消費(fèi)者拿取元素時(shí)涨醋,如果沒(méi)有元素瓜饥,同樣也會(huì)阻塞。
ArrayBlockingQueue
是一個(gè)用數(shù)組實(shí)現(xiàn)的有界阻塞隊(duì)列浴骂。此隊(duì)列按照先進(jìn)先出(FIFO)的原則對(duì)元素進(jìn)行排序乓土。默認(rèn)情況下不保證線程公平的訪問(wèn)隊(duì)列,所謂公平訪問(wèn)隊(duì)列是指阻塞的線程溯警,可以按照阻塞的先后順序訪問(wèn)隊(duì)列趣苏,即先阻塞線程先訪問(wèn)隊(duì)列。非公平性是對(duì)先等待的線程是非公平的梯轻,當(dāng)隊(duì)列可用時(shí)食磕,阻塞的線程都可以爭(zhēng)奪訪問(wèn)隊(duì)列的資格,有可能先阻塞的線程最后才訪問(wèn)隊(duì)列喳挑。初始化時(shí)有參數(shù)可以設(shè)置
LinkedBlockingQueue
是一個(gè)用鏈表實(shí)現(xiàn)的有界阻塞隊(duì)列彬伦。此隊(duì)列的默認(rèn)和最大長(zhǎng)度為Integer.MAX_VALUE滔悉。此隊(duì)列按照先進(jìn)先出的原則對(duì)元素進(jìn)行排序。
Array實(shí)現(xiàn)和Linked實(shí)現(xiàn)的區(qū)別
1. 隊(duì)列中鎖的實(shí)現(xiàn)不同
ArrayBlockingQueue實(shí)現(xiàn)的隊(duì)列中的鎖是沒(méi)有分離的单绑,即生產(chǎn)和消費(fèi)用的是同一個(gè)鎖回官;
LinkedBlockingQueue實(shí)現(xiàn)的隊(duì)列中的鎖是分離的,即生產(chǎn)用的是putLock搂橙,消費(fèi)是takeLock
Condition是AQS的內(nèi)部類歉提。每個(gè)Condition對(duì)象都包含一個(gè)隊(duì)列(等待隊(duì)列)。
等待隊(duì)列是一個(gè)FIFO的隊(duì)列区转,在隊(duì)列中的每個(gè)節(jié)點(diǎn)都包含了一個(gè)線程引用唯袄,
該線程就是在Condition對(duì)象上等待的線程,如果一個(gè)線程調(diào)用了Condition.await()方法蜗帜,
那么該線程將會(huì)釋放鎖、構(gòu)造成節(jié)點(diǎn)加入等待隊(duì)列并進(jìn)入等待狀態(tài)资厉。
等待隊(duì)列的基本結(jié)構(gòu)如下所示厅缺。
2. 在生產(chǎn)或消費(fèi)時(shí)操作不同
ArrayBlockingQueue實(shí)現(xiàn)的隊(duì)列中在生產(chǎn)和消費(fèi)的時(shí)候,是直接將枚舉對(duì)象插入或移除的宴偿;
LinkedBlockingQueue實(shí)現(xiàn)的隊(duì)列中在生產(chǎn)和消費(fèi)的時(shí)候湘捎,需要把枚舉對(duì)象轉(zhuǎn)換為Node<E>進(jìn)行插入或移除,會(huì)影響性能
3. 隊(duì)列大小初始化方式不同
ArrayBlockingQueue實(shí)現(xiàn)的隊(duì)列中必須指定隊(duì)列的大姓酢窥妇;
LinkedBlockingQueue實(shí)現(xiàn)的隊(duì)列中可以不指定隊(duì)列的大小,但是默認(rèn)是Integer.MAX_VALUE
PriorityBlockingQueue
PriorityBlockingQueue是一個(gè)支持優(yōu)先級(jí)的無(wú)界阻塞隊(duì)列娩践。默認(rèn)情況下元素采取自然順序升序排列活翩。也可以自定義類實(shí)現(xiàn)compareTo()方法來(lái)指定元素排序規(guī)則,或者初始化PriorityBlockingQueue時(shí)翻伺,指定構(gòu)造參數(shù)Comparator來(lái)對(duì)元素進(jìn)行排序材泄。需要注意的是不能保證同優(yōu)先級(jí)元素的順序。
DelayQueue
是一個(gè)支持延時(shí)獲取元素的無(wú)界阻塞隊(duì)列吨岭。隊(duì)列使用PriorityQueue來(lái)實(shí)現(xiàn)拉宗。隊(duì)列中的元素必須實(shí)現(xiàn)Delayed接口,在創(chuàng)建元素時(shí)可以指定多久才能從隊(duì)列中獲取當(dāng)前元素辣辫。只有在延遲期滿時(shí)才能從隊(duì)列中提取元素旦事。
DelayQueue非常有用,可以將DelayQueue運(yùn)用在以下應(yīng)用場(chǎng)景急灭。
緩存系統(tǒng)的設(shè)計(jì):可以用DelayQueue保存緩存元素的有效期姐浮,使用一個(gè)線程循環(huán)查詢DelayQueue,一旦能從DelayQueue中獲取元素時(shí)化戳,表示緩存有效期到了单料。
SynchronousQueue
是一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列埋凯。每一個(gè)put操作必須等待一個(gè)take操作,否則不能繼續(xù)添加元素扫尖。SynchronousQueue可以看成是一個(gè)傳球手白对,負(fù)責(zé)把生產(chǎn)者線程處理的數(shù)據(jù)直接傳遞給消費(fèi)者線程。隊(duì)列本身并不存儲(chǔ)任何元素换怖,非常適合傳遞性場(chǎng)景甩恼。
LinkedTransferQueue
多了tryTransfer和transfer方法,
(1)transfer方法
如果當(dāng)前有消費(fèi)者正在等待接收元素(消費(fèi)者使用take()方法或帶時(shí)間限制的poll()方法時(shí))沉颂,transfer方法可以把生產(chǎn)者傳入的元素立刻transfer(傳輸)給消費(fèi)者条摸。如果沒(méi)有消費(fèi)者在等待接收元素,transfer方法會(huì)將元素存放在隊(duì)列的tail節(jié)點(diǎn)铸屉,并等到該元素被消費(fèi)者消費(fèi)了才返回钉蒲。
(2)tryTransfer方法
tryTransfer方法是用來(lái)試探生產(chǎn)者傳入的元素是否能直接傳給消費(fèi)者。如果沒(méi)有消費(fèi)者等待接收元素彻坛,則返回false顷啼。和transfer方法的區(qū)別是tryTransfer方法無(wú)論消費(fèi)者是否接收,方法立即返回昌屉,而transfer方法是必須等到消費(fèi)者消費(fèi)了才返回钙蒙。
LinkedBlockingDeque
LinkedBlockingDeque是一個(gè)由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。所謂雙向隊(duì)列指的是可以從隊(duì)列的兩端插入和移出元素间驮。雙向隊(duì)列因?yàn)槎嗔艘粋€(gè)操作隊(duì)列的入口躬厌,在多線程同時(shí)入隊(duì)時(shí),也就減少了一半的競(jìng)爭(zhēng)竞帽。
多了addFirst扛施、addLast、offerFirst屹篓、offerLast煮嫌、peekFirst和peekLast等方法,以First單詞結(jié)尾的方法抱虐,表示插入昌阿、獲取(peek)或移除雙端隊(duì)列的第一個(gè)元素恳邀。以Last單詞結(jié)尾的方法懦冰,表示插入、獲取或移除雙端隊(duì)列的最后一個(gè)元素谣沸。另外刷钢,插入方法add等同于addLast,移除方法remove等效于removeFirst乳附。但是take方法卻等同于takeFirst内地,不知道是不是JDK的bug伴澄,使用時(shí)還是用帶有First和Last后綴的方法更清楚。在初始化LinkedBlockingDeque時(shí)可以設(shè)置容量防止其過(guò)度膨脹阱缓。另外非凌,雙向阻塞隊(duì)列可以運(yùn)用在“工作竊取”模式中。
線程池
為什么要用線程池荆针? Rxjava的線程切換底層就是線程池敞嗡,所以在牛逼,都是逃不出java的生態(tài)
Java中的線程池是運(yùn)用場(chǎng)景最多的并發(fā)框架航背,幾乎所有需要異步或并發(fā)執(zhí)行任務(wù)的程序都可以使用線程池喉悴。在開發(fā)過(guò)程中,合理地使用線程池能夠帶來(lái)3個(gè)好處玖媚。
- 第一:降低資源消耗箕肃。通過(guò)重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
- 第二:提高響應(yīng)速度今魔。當(dāng)任務(wù)到達(dá)時(shí)突雪,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。假設(shè)一個(gè)服務(wù)器完成一項(xiàng)任務(wù)所需時(shí)間為:T1 創(chuàng)建線程時(shí)間涡贱,T2 在線程中執(zhí)行任務(wù)的時(shí)間,T3 銷毀線程時(shí)間惹想。 如果:T1 + T3 遠(yuǎn)大于 T2问词,則可以采用線程池,以提高服務(wù)器性能嘀粱。線程池技術(shù)正是關(guān)注如何縮短或調(diào)整T1,T3時(shí)間的技術(shù)激挪,從而提高服務(wù)器程序性能的。它把T1锋叨,T3分別安排在服務(wù)器程序的啟動(dòng)和結(jié)束的時(shí)間段或者一些空閑的時(shí)間段垄分,這樣在服務(wù)器程序處理客戶請(qǐng)求時(shí),不會(huì)有T1娃磺,T3的開銷了薄湿。
- 第三:提高線程的可管理性。線程是稀缺資源偷卧,如果無(wú)限制地創(chuàng)建豺瘤,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性听诸,使用線程池可以進(jìn)行統(tǒng)一分配坐求、調(diào)優(yōu)和監(jiān)控。
各個(gè)參數(shù)的含義
-
corePoolSize
線程池中的核心線程數(shù)晌梨,當(dāng)提交一個(gè)任務(wù)時(shí)桥嗤,線程池創(chuàng)建一個(gè)新線程執(zhí)行任務(wù)须妻,直到當(dāng)前線程數(shù)等于corePoolSize;
如果當(dāng)前線程數(shù)為corePoolSize泛领,繼續(xù)提交的任務(wù)被保存到阻塞隊(duì)列中荒吏,等待被執(zhí)行;
如果執(zhí)行了線程池的prestartAllCoreThreads()方法师逸,線程池會(huì)提前創(chuàng)建并啟動(dòng)所有核心線程司倚。
啟動(dòng)所有核心線程,使它們空閑地等待工作篓像。 這個(gè) 僅在以下情況下覆蓋啟動(dòng)核心線程的默認(rèn)策略: 執(zhí)行新任務(wù)动知。 返回啟動(dòng)的線程數(shù)
-
maximumPoolSize
線程池中允許的最大線程數(shù)。如果當(dāng)前阻塞隊(duì)列滿了员辩,且繼續(xù)提交任務(wù)盒粮,則創(chuàng)建新的線程執(zhí)行任務(wù),前提是當(dāng)前線程數(shù)小于maximumPoolSize,如果不是的話奠滑,會(huì)直接拋出異常
keepAliveTime
線程空閑時(shí)的存活時(shí)間丹皱,即當(dāng)線程沒(méi)有任務(wù)執(zhí)行時(shí),繼續(xù)存活的時(shí)間宋税,這個(gè)有個(gè)問(wèn)題摊崭,當(dāng)我們的目前存活的線程數(shù)<=croePoolSize,那肯定是不會(huì)執(zhí)行的杰赛,這個(gè)參數(shù)是無(wú)效的呢簸,一般是超過(guò)了核心線程池的線程數(shù)量,你不能把核心存活的線程給關(guān)閉了吧-
TimeUnit
keepAliveTime的時(shí)間單位 乏屯,這個(gè)類很有意思根时,是個(gè)枚舉類,李大爺在寫這個(gè)類的時(shí)候辰晕,我能感受他的心態(tài)蛤迎,我他媽太有才了,這樣都被我想到了
我在八一八這個(gè)有意思的事情含友,2004年9月30日18:00PM替裆,J2SE1.5發(fā)布,成為Java語(yǔ)言發(fā)展史上的又一里程碑窘问。為了表示該版本的重要性扎唾,J2SE1.5更名為Java SE 5.0
Java從5.0版本開始,加入許多新特性南缓,是Java歷史中修改最大的版本胸遇,許多特點(diǎn)模仿自C#,因而被認(rèn)為是為了與C#對(duì)抗汉形。
-
枚舉類型也是J2SE 5.0的新功能纸镊。過(guò)去Java認(rèn)為enum的關(guān)鍵字是不必要的功能倍阐,因?yàn)橛胮ublic static int field就可以取代enum,因此過(guò)去一直不用逗威。J2SE 5.0中的class如果是enum峰搪,在class file中會(huì)被粘貼一個(gè)ACC_ENUM標(biāo)簽。Enum 一般用來(lái)表示一組相同類型的常量凯旭。如性別概耻、日期、月份罐呼、顏色等鞠柄。對(duì)這些屬性用常量的好處是顯而易見(jiàn)的,不僅可以保證單例嫉柴,且比較時(shí)候可以用 “==” 來(lái)替換 equals 厌杜。是一種好的習(xí)慣。 JDK1.5 之前沒(méi)有 Enum 這個(gè)類型计螺,那時(shí)候一般用接口常量來(lái)替代夯尽。Java有了Enum 之后,可以更貼近的表示這種常量登馒。
但是但是 :自動(dòng)裝箱/拆箱的矛盾:為什么你比較大小就是 比數(shù)字的大小匙握,但是!=陈轿,,你卻比較的是內(nèi)存地址圈纺??济欢? 不科學(xué)呀
Integer int1 = new Integer(1);
Integer int2 = new Integer(1);
//為什么你比較大小就是 比數(shù)字的大小,但是小渊!=法褥,,你卻比較的是內(nèi)存地址?酬屉?半等? 不科學(xué)呀
System.out.println(int1 >= int2); // 檢查兩者的值, true
System.out.println(int1 <= int2); // 檢查兩者的值呐萨, true
System.out.println(int1 != int2); // 檢查兩者的參考位置杀饵,true 我認(rèn)為這里為false 1難道不等于1么
System.out.println(int1 == int2); // 檢查兩者的參考位置,false
ExecutorService executorService = Executors.newFixedThreadPool(1);
- corePoolSize與maximumPoolSize相等谬擦,即其線程全為核心線程切距,是一個(gè)固定大小的線程池,是其優(yōu)勢(shì)惨远;
-
keepAliveTime = 0 該參數(shù)默認(rèn)對(duì)核心線程無(wú)效谜悟,而FixedThreadPool全部為核心線程话肖;為什么?
因?yàn)閭魅氲氖?:TimeUnit.MILLISECONDS
也就是調(diào)用了 toNanos
方法葡幸,也就是 x的方法最筒,其實(shí)都是返回0
workQueue
workQueue必須是BlockingQueue阻塞隊(duì)列。當(dāng)線程池中的線程數(shù)超過(guò)它的corePoolSize的時(shí)候蔚叨,線程會(huì)進(jìn)入阻塞隊(duì)列進(jìn)行阻塞等待床蜘。通過(guò)workQueue,線程池實(shí)現(xiàn)了阻塞功能蔑水。
一般來(lái)說(shuō)邢锯,我們應(yīng)該盡量使用有界隊(duì)列,因?yàn)槭褂脽o(wú)界隊(duì)列作為工作隊(duì)列會(huì)對(duì)線程池帶來(lái)如下影響肤粱。
1)當(dāng)線程池中的線程數(shù)達(dá)到corePoolSize后弹囚,新任務(wù)將在無(wú)界隊(duì)列中等待,因此線程池中的線程數(shù)不會(huì)超過(guò)corePoolSize领曼。
2)由于1鸥鹉,使用無(wú)界隊(duì)列時(shí)maximumPoolSize將是一個(gè)無(wú)效參數(shù)。
3)由于1和2庶骄,使用無(wú)界隊(duì)列時(shí)keepAliveTime將是一個(gè)無(wú)效參數(shù)毁渗。
4)更重要的,使用無(wú)界queue可能會(huì)耗盡系統(tǒng)資源单刁,有界隊(duì)列則有助于防止資源耗盡灸异,同時(shí)即使使用有界隊(duì)列,也要盡量控制隊(duì)列的大小在一個(gè)合適的范圍羔飞。threadFactory
創(chuàng)建線程的工廠肺樟,通過(guò)自定義的線程工廠可以給每個(gè)新建的線程設(shè)置一個(gè)具有識(shí)別度的線程名,當(dāng)然還可以更加自由的對(duì)線程做更多的設(shè)置逻淌,比如設(shè)置所有的線程為守護(hù)線程么伯。
Executors靜態(tài)工廠里默認(rèn)的threadFactory,線程的命名規(guī)則是“pool-數(shù)字-thread-數(shù)字”卡儒。RejectedExecutionHandler
線程池的飽和策略田柔,當(dāng)阻塞隊(duì)列滿了,且沒(méi)有空閑的工作線程骨望,如果繼續(xù)提交任務(wù)硬爆,必須采取一種策略處理該任務(wù),線程池提供了4種策略:
(1)AbortPolicy:直接拋出異常擎鸠,默認(rèn)策略缀磕;
(2)CallerRunsPolicy:用調(diào)用者所在的線程來(lái)執(zhí)行任務(wù);
(3)DiscardOldestPolicy:丟棄阻塞隊(duì)列中靠最前的任務(wù),并執(zhí)行當(dāng)前任務(wù)虐骑;
(4)DiscardPolicy:直接丟棄任務(wù)准验;
當(dāng)然也可以根據(jù)應(yīng)用場(chǎng)景實(shí)現(xiàn)RejectedExecutionHandler接口,自定義飽和策略廷没,如記錄日志或持久化存儲(chǔ)不能處理的任務(wù)糊饱。
線程池的工作機(jī)制
1)如果當(dāng)前運(yùn)行的線程少于corePoolSize,則創(chuàng)建新線程來(lái)執(zhí)行任務(wù)(注意颠黎,執(zhí)行這一步驟需要獲取全局鎖)另锋。
2)如果運(yùn)行的線程等于或多于corePoolSize,則將任務(wù)加入BlockingQueue狭归。
3)如果無(wú)法將任務(wù)加入BlockingQueue(隊(duì)列已滿),則創(chuàng)建新的線程來(lái)處理任務(wù)。
4)如果創(chuàng)建新線程將使當(dāng)前運(yùn)行的線程超出maximumPoolSize,任務(wù)將被拒絕,并調(diào)用RejectedExecutionHandler.rejectedExecution()方法勾哩。
提交任務(wù)
execute()方法用于提交不需要返回值的任務(wù)思劳,所以無(wú)法判斷任務(wù)是否被線程池執(zhí)行成功潜叛。
submit()方法用于提交需要返回值的任務(wù)森瘪。線程池會(huì)返回一個(gè)future類型的對(duì)象,通過(guò)這個(gè)future對(duì)象可以判斷任務(wù)是否執(zhí)行成功窗宇,并且可以通過(guò)future的get()方法來(lái)獲取返回值军俊,get()方法會(huì)阻塞當(dāng)前線程直到任務(wù)完成提前,而使用get(long timeout,TimeUnit unit)方法則會(huì)阻塞當(dāng)前線程一段時(shí)間后立即返回,這時(shí)候有可能任務(wù)沒(méi)有執(zhí)行完础淤。
關(guān)閉線程池
可以通過(guò)調(diào)用線程池的shutdown或shutdownNow方法來(lái)關(guān)閉線程池鸽凶。它們的原理是遍歷線程池中的工作線程边锁,然后逐個(gè)調(diào)用線程的interrupt方法來(lái)中斷線程曹鸠,所以無(wú)法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無(wú)法終止煌茬。但是它們存在一定的區(qū)別,shutdownNow首先將線程池的狀態(tài)設(shè)置成STOP彻桃,然后嘗試停止所有的正在執(zhí)行或暫停任務(wù)的線程坛善,并返回等待執(zhí)行任務(wù)的列表,而shutdown只是將線程池的狀態(tài)設(shè)置成SHUTDOWN狀態(tài)邻眷,然后中斷所有沒(méi)有正在執(zhí)行任務(wù)的線程浑吟。
只要調(diào)用了這兩個(gè)關(guān)閉方法中的任意一個(gè),isShutdown方法就會(huì)返回true耗溜。當(dāng)所有的任務(wù)都已關(guān)閉后组力,才表示線程池關(guān)閉成功,這時(shí)調(diào)用isTerminaed方法會(huì)返回true抖拴。至于應(yīng)該調(diào)用哪一種方法來(lái)關(guān)閉線程池燎字,應(yīng)該由提交到線程池的任務(wù)特性決定,通常調(diào)用shutdown方法來(lái)關(guān)閉線程池阿宅,如果任務(wù)不一定要執(zhí)行完候衍,則可以調(diào)用shutdownNow方法。
合理地配置線程池
要想合理地配置線程池洒放,就必須首先分析任務(wù)特性
要想合理地配置線程池蛉鹿,就必須首先分析任務(wù)特性,可以從以下幾個(gè)角度來(lái)分析往湿。
? 任務(wù)的性質(zhì):CPU密集型任務(wù)妖异、IO密集型任務(wù)和混合型任務(wù)。
? 任務(wù)的優(yōu)先級(jí):高领追、中和低他膳。
? 任務(wù)的執(zhí)行時(shí)間:長(zhǎng)、中和短绒窑。
? 任務(wù)的依賴性:是否依賴其他系統(tǒng)資源棕孙,如數(shù)據(jù)庫(kù)連接。
性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開處理些膨。
-
CPU密集型任務(wù)應(yīng)配置盡可能小的線程蟀俊,如配置Ncpu+1個(gè)線程的線程池。由于IO密集型任務(wù)線程并不是一直在執(zhí)行任務(wù)订雾,則應(yīng)配置盡可能多的線程肢预,如2Ncpu.*
混合型的任務(wù),如果可以拆分葬燎,將其拆分成一個(gè)CPU密集型任務(wù)和一個(gè)IO密集型任務(wù)误甚,只要這兩個(gè)任務(wù)執(zhí)行的時(shí)間相差不是太大,那么分解后執(zhí)行的吞吐量將高于串行執(zhí)行的吞吐量谱净。如果這兩個(gè)任務(wù)執(zhí)行時(shí)間相差太大窑邦,則沒(méi)必要進(jìn)行分解『咎剑可以通過(guò)Runtime.getRuntime().availableProcessors()
方法獲得當(dāng)前設(shè)備的CPU個(gè)數(shù)冈钦。
優(yōu)先級(jí)不同的任務(wù)可以使用優(yōu)先級(jí)隊(duì)列PriorityBlockingQueue來(lái)處理。它可以讓優(yōu)先級(jí)高的任務(wù)先執(zhí)行李请。
執(zhí)行時(shí)間不同的任務(wù)可以交給不同規(guī)模的線程池來(lái)處理瞧筛,或者可以使用優(yōu)先級(jí)隊(duì)列,讓執(zhí)行時(shí)間短的任務(wù)先執(zhí)行导盅。
建議使用有界隊(duì)列较幌。有界隊(duì)列能增加系統(tǒng)的穩(wěn)定性和預(yù)警能力,可以根據(jù)需要設(shè)大一點(diǎn)兒白翻,比如幾千乍炉。
如果當(dāng)時(shí)我們?cè)O(shè)置成無(wú)界隊(duì)列,那么線程池的隊(duì)列就會(huì)越來(lái)越多滤馍,有可能會(huì)撐滿內(nèi)存岛琼,導(dǎo)致整個(gè)系統(tǒng)不可用,而不只是后臺(tái)任務(wù)出現(xiàn)問(wèn)題巢株。
ThreadPoolExecutor 的類關(guān)系
public interface Executor
Executor是一個(gè)接口槐瑞,它是Executor框架的基礎(chǔ),它將任務(wù)的提交與任務(wù)的執(zhí)行分離開來(lái)阁苞。
ExecutorService接口繼承了Executor困檩,在其上做了一些shutdown()、submit()的擴(kuò)展那槽,可以說(shuō)是真正的線程池接口窗看;
AbstractExecutorService抽象類實(shí)現(xiàn)了ExecutorService接口中的大部分方法;
ThreadPoolExecutor是線程池的核心實(shí)現(xiàn)類倦炒,用來(lái)執(zhí)行被提交的任務(wù)
ScheduledExecutorService接口繼承了ExecutorService接口显沈,提供了帶"周期執(zhí)行"功能ExecutorService;
ScheduledThreadPoolExecutor是一個(gè)實(shí)現(xiàn)類逢唤,可以在給定的延遲后運(yùn)行命令拉讯,或者定期執(zhí)行命令。ScheduledThreadPoolExecutor比Timer更靈活鳖藕,功能更強(qiáng)大魔慷。
ScheduledThreadPoolExecutor
可以用來(lái)在給定延時(shí)后執(zhí)行異步任務(wù)或者周期性執(zhí)行任務(wù),相對(duì)于任務(wù)調(diào)度的Timer來(lái)說(shuō)著恩,其功能更加強(qiáng)大院尔,Timer只能使用一個(gè)后臺(tái)線程執(zhí)行任務(wù)蜻展,而ScheduledThreadPoolExecutor
則可以通過(guò)構(gòu)造函數(shù)來(lái)指定后臺(tái)線程的個(gè)數(shù)。
線程池的內(nèi)部使用的CAS的原理(比較并交換(compare and swap, CAS)邀摆,是原子操作的一種纵顾,可用于在多線程編程中實(shí)現(xiàn)不被打斷的數(shù)據(jù)交換操作,從而避免多線程同時(shí)改寫某一數(shù)據(jù)時(shí)由于執(zhí)行順序不確定性以及中斷的不可預(yù)知性產(chǎn)生的數(shù)據(jù)不一致問(wèn)題栋盹。 該操作通過(guò)將內(nèi)存中的值與指定數(shù)據(jù)進(jìn)行比較施逾,當(dāng)數(shù)值一樣時(shí)將內(nèi)存中的數(shù)據(jù)替換為新的值。)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
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例获、一個(gè)新的任務(wù)會(huì)放到corePool,核心線程池中
2汉额、當(dāng)核心線程池滿了,就會(huì)放到BlockingQueue榨汤,如果BlockingQueue是一個(gè)有界隊(duì)列者祖,而且也滿了的話
3秆撮、就是在小于最大線程池里面創(chuàng)建新的任務(wù)捉腥,直到最大線程池也滿了
4肠虽、就會(huì)去拒絕隊(duì)列中,看是哪種的拒絕的方案啼器,然后就這樣