? ? ? ?這段時(shí)間一直在忙著自己的事情,搞的非常尷尬,一直沒(méi)能靜下心來(lái)好好想想。不過(guò)還好,最近對(duì)于線程池的東西還是一直處于進(jìn)步階段。
? ? ? ? 我還是習(xí)慣直接動(dòng)嘴說(shuō)說(shuō),以后萬(wàn)一想開(kāi)了,也會(huì)貼很多源碼上來(lái),但是現(xiàn)在主要還是想打字來(lái)解釋,畢竟源碼很多時(shí)候來(lái)的更清晰,但是我這樣寫(xiě)的目的也是等于加強(qiáng)自己的記憶,所以先不用源碼來(lái)增加篇幅了。
? ? ? ? 還是先new一個(gè)線程池開(kāi)始吧。new ThreadPoolExecutor(0,0,0,null,new SynchronousQueue<Runnable>());0和null是我上一篇已經(jīng)詳細(xì)說(shuō)的東西了,現(xiàn)在實(shí)在是懶得給他多一點(diǎn)賦值的心情了。對(duì),沒(méi)錯(cuò),我是打算說(shuō)一下最后這個(gè)隊(duì)列的。
? ? ? ? 這個(gè)隊(duì)列,目前最常見(jiàn)的有三種(ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue),在使用executors創(chuàng)建的線程池中,你會(huì)發(fā)現(xiàn)沒(méi)有第一種,具體原因不得而知,可能是:一、限制的太死?因?yàn)閍rray對(duì)列是固定緩存池大小的(因?yàn)闆](méi)人會(huì)問(wèn)這個(gè)隊(duì)列是干嘛的了吧,好吧,我說(shuō)一句,這個(gè)是用來(lái)緩存任務(wù)的,只有當(dāng)任務(wù)填滿緩存了,才會(huì)用到maxinumpoolsize,上一篇。。。)。這種固定死的方式會(huì)導(dǎo)致很多意想不到的拒絕(線程池對(duì)任務(wù)的拒絕措施,一般都是默認(rèn)的----拋異常拒絕)。而且我感覺(jué)推薦使用executors也是有一種不在拒絕的味道(個(gè)人主觀臆斷,沒(méi)什么依據(jù),主要是從創(chuàng)建的幾種線程池的內(nèi)容中推斷的,畢竟我只是一個(gè)渣渣,后面我會(huì)說(shuō)一點(diǎn)點(diǎn)推理的依據(jù))。二、設(shè)計(jì)不好(求不打臉,我這個(gè)瞎說(shuō)的),看過(guò)源碼的童鞋應(yīng)該發(fā)現(xiàn)了,array隊(duì)列的里面只用一把鎖final修飾的。這樣這個(gè)隊(duì)列在添加和移除的時(shí)候都是同一把鎖,也就是不能出現(xiàn)添加和移除的并發(fā)操作,我大致百度了一下,沒(méi)有找到合理的解釋。這樣可能會(huì)在線程池頻繁添加和移除的時(shí)候出現(xiàn)性能問(wèn)題。當(dāng)然這個(gè)是我瞎說(shuō)的,不注意作為依據(jù)。
? ? ? ? 那我們?cè)賮?lái)談?wù)勈O碌膬蓚€(gè)queue吧。linkedBlockingQueue,一看名字就知道,這個(gè)是鏈表的隊(duì)列嘛,對(duì),就是這樣的,那有什么說(shuō)的呢?首先說(shuō)一下鎖的問(wèn)題,這個(gè)隊(duì)列就很理智(233)的使用了兩個(gè)final的lock(take和put),對(duì)嘛,進(jìn)出分開(kāi)嘛,當(dāng)年我在化工廠里面看設(shè)計(jì)圖的時(shí)候,就看到人家設(shè)計(jì)師說(shuō),人流物流進(jìn)進(jìn)出出嚴(yán)格分開(kāi)滴,沒(méi)錯(cuò),程序就是要反應(yīng)現(xiàn)實(shí)的,不然寫(xiě)程序不就找不到使用的價(jià)值了(繼續(xù)瞎說(shuō))。然后再來(lái)一點(diǎn)不瞎說(shuō)的。既然是鏈表,我們存取是不是可以使用雙向鏈表尾放頭取呢?可以,沒(méi)錯(cuò),人家就是這么玩的,是不是一點(diǎn)都不意外,你能想到的這么簡(jiǎn)單的東西,人家大牛早就實(shí)現(xiàn)了。那么,這樣是不是就有一個(gè)很熟悉的生產(chǎn)者消費(fèi)者模式出現(xiàn)了,哇塞,如果你能想到這里,那么和我這個(gè)渣渣同一水準(zhǔn)了呢(2333),反正寫(xiě)東西的時(shí)候,瞎想一下,萬(wàn)一和大神想到一起去了呢。對(duì),這里就是這樣的。通過(guò)前面的兩把鎖(顯示鎖aqs實(shí)現(xiàn)的ReentrantLock,我打算下一次寫(xiě)一下這個(gè)),獲取condition(用來(lái)休息的),然后每次存/取的時(shí)候,檢查一下容量,如果滿/虧,那么就休息一下。至于為什么是循環(huán),那是因?yàn)檫@里有并發(fā),每次醒來(lái)的時(shí)候,先循環(huán)一下,能不能拿到,拿不到就繼續(xù)休息。如果拿到了,就去干活吧。take和put最后一行代碼是叫醒對(duì)應(yīng)的線程,這個(gè)是因?yàn)檫@個(gè)線程已經(jīng)拿走了一個(gè),所以讓等著的put填進(jìn)來(lái)。(這里我專門提一下的原因是我第一次就看錯(cuò)了,搞了半天才發(fā)現(xiàn)是自己atomicInteger的方法getAndDecrement搞錯(cuò)了---拿到舊值(我理解成了拿到增加之后的值了)),所以這里我專門提示一下。然后這個(gè)隊(duì)列就沒(méi)有太多可以說(shuō)的了,而且實(shí)現(xiàn)也很簡(jiǎn)單,看一遍源碼就知道了。
? ? ? ? 剩下一個(gè)是SynchronousQueue,這個(gè)沒(méi)有加blocking,是不是以為就不是阻塞的呢?這個(gè)隊(duì)列很特殊,它是一個(gè)轉(zhuǎn)移隊(duì)列,自己不保存東西,也就是容量是0,。那他怎么玩的呢?首先通過(guò)內(nèi)部transfer對(duì)象來(lái)實(shí)現(xiàn)(queue公平,stack非公平)隊(duì)列。說(shuō)好的不保存東西的呢,是的,任務(wù)過(guò)來(lái)了,它是不會(huì)保存的。那么他保存的是什么呢,沒(méi)錯(cuò),是空閑的線程。這個(gè)隊(duì)列通過(guò)put的東西會(huì)一直阻塞自己,只有當(dāng)下一個(gè)線程過(guò)來(lái)take之后才會(huì)繼續(xù)自己的邏輯。所以當(dāng)新任務(wù)來(lái)的時(shí)候,這個(gè)隊(duì)列直接創(chuàng)建新的線程去執(zhí)行,自己不緩存任務(wù)。而當(dāng)空閑線程回來(lái)的時(shí)候,那就可以進(jìn)隊(duì)了,因?yàn)閜ut就是await當(dāng)前線程的,這個(gè)是通過(guò)lockResource也就是底層native方法的wait,因?yàn)檫@個(gè)隊(duì)列沒(méi)有使用aqs,直接是使用的cas算法,代碼那叫一個(gè)復(fù)雜呀。不過(guò)慢慢也能看懂,就是node的進(jìn)出隊(duì)列。算是一個(gè)優(yōu)化鎖的實(shí)現(xiàn),先自旋一會(huì)然后等待(根據(jù)cpu數(shù)定次數(shù),如果單cpu,就不要自旋了,直接wait吧,其他的就是需要的),這個(gè)設(shè)計(jì)就非常合理,因?yàn)閱蝐pu如果大量的自旋,會(huì)明顯拖慢其他的線程。至于這個(gè)隊(duì)列的公平模式和非公平模式,我就不細(xì)說(shuō)了,感覺(jué)和鎖的機(jī)制差不多,只是他使用了兩種數(shù)據(jù)結(jié)構(gòu)罷了。
? ? ? ? 說(shuō)了這么多廢話,現(xiàn)在再說(shuō)一下java的推薦的幾種選擇,單線程和固定容量的線程都是選擇的是LinkedBlockingQueue,沒(méi)有大小上限,默認(rèn)是integer.MAX_VALUE(當(dāng)然可以設(shè)置大小的),前面我說(shuō)的不建議使用拒絕措施就是這里看到的,因?yàn)槿绻诵某睾妥畲蟪毓潭?,就不再固定緩存的?shù)量了,這樣在正常的情況就不會(huì)有什么拒絕的機(jī)會(huì)了。至于cached的線程池,那還用說(shuō),雖然沒(méi)有核心線程,但是我們最大線程多呀,也是integer.MAX_VALUE(其實(shí)線程池沒(méi)有這么大的容量,因?yàn)榫€程池容量和狀態(tài)是一個(gè)integer,32位,高位3個(gè)位置保存status,低位29個(gè)位置保存容量,這種設(shè)計(jì),省了一個(gè)atmicinteger的對(duì)象,但是對(duì)于我這種渣渣,看起源碼來(lái),苦的一批),所以,這個(gè)緩存池也沒(méi)有機(jī)會(huì)拒絕,對(duì)了,差點(diǎn)忘記說(shuō)了,這個(gè)隊(duì)列使用的就是SynchronousQueue,每次過(guò)來(lái)任務(wù)也不緩存了,直接創(chuàng)建新線程就可以了,反正我們線程多,用來(lái)了過(guò)期時(shí)間到了也自動(dòng)銷毀了。不過(guò)這種方法其實(shí)不是很好,因?yàn)閷?duì)于任務(wù)時(shí)間長(zhǎng)的任務(wù),會(huì)出現(xiàn)大量的運(yùn)行和就緒的線程,導(dǎo)致其他的線程運(yùn)行速度受限,所以最好使用這個(gè)線程池的時(shí)候,執(zhí)行的都是小任務(wù)。
? ? ? ? 好了,線程池就到這里吧,其實(shí)很多線程池的東西可以寫(xiě),但是寫(xiě)起來(lái)真的沒(méi)完沒(méi)了了,比如每一個(gè)線程的創(chuàng)建都是一個(gè)work,work不單單實(shí)現(xiàn)了runnable接口,還繼承了aqs,沒(méi)錯(cuò),每一個(gè)任務(wù)本身就是一把鎖,而且是排他鎖。好了,大致就這么多了,下一篇,我們聊聊鎖吧。
? ??????