? ? ? ?這段時間一直在忙著自己的事情腊脱,搞的非常尷尬访得,一直沒能靜下心來好好想想。不過還好陕凹,最近對于線程池的東西還是一直處于進步階段悍抑。
? ? ? ? 我還是習慣直接動嘴說說,以后萬一想開了杜耙,也會貼很多源碼上來搜骡,但是現(xiàn)在主要還是想打字來解釋,畢竟源碼很多時候來的更清晰佑女,但是我這樣寫的目的也是等于加強自己的記憶记靡,所以先不用源碼來增加篇幅了谈竿。
? ? ? ? 還是先new一個線程池開始吧。new ThreadPoolExecutor(0摸吠,0,0空凸,null,new SynchronousQueue<Runnable>());0和null是我上一篇已經(jīng)詳細說的東西了蜕便,現(xiàn)在實在是懶得給他多一點賦值的心情了劫恒。對,沒錯轿腺,我是打算說一下最后這個隊列的两嘴。
? ? ? ? 這個隊列,目前最常見的有三種(ArrayBlockingQueue族壳,LinkedBlockingQueue憔辫,SynchronousQueue),在使用executors創(chuàng)建的線程池中仿荆,你會發(fā)現(xiàn)沒有第一種贰您,具體原因不得而知,可能是:一拢操、限制的太死锦亦?因為array對列是固定緩存池大小的(因為沒人會問這個隊列是干嘛的了吧,好吧令境,我說一句杠园,這個是用來緩存任務的,只有當任務填滿緩存了舔庶,才會用到maxinumpoolsize抛蚁,上一篇。惕橙。瞧甩。)。這種固定死的方式會導致很多意想不到的拒絕(線程池對任務的拒絕措施弥鹦,一般都是默認的----拋異常拒絕)肚逸。而且我感覺推薦使用executors也是有一種不在拒絕的味道(個人主觀臆斷,沒什么依據(jù)惶凝,主要是從創(chuàng)建的幾種線程池的內(nèi)容中推斷的吼虎,畢竟我只是一個渣渣,后面我會說一點點推理的依據(jù))苍鲜。二、設計不好(求不打臉玷犹,我這個瞎說的)混滔,看過源碼的童鞋應該發(fā)現(xiàn)了洒疚,array隊列的里面只用一把鎖final修飾的。這樣這個隊列在添加和移除的時候都是同一把鎖坯屿,也就是不能出現(xiàn)添加和移除的并發(fā)操作油湖,我大致百度了一下,沒有找到合理的解釋领跛。這樣可能會在線程池頻繁添加和移除的時候出現(xiàn)性能問題乏德。當然這個是我瞎說的,不注意作為依據(jù)吠昭。
? ? ? ? 那我們再來談談剩下的兩個queue吧喊括。linkedBlockingQueue,一看名字就知道矢棚,這個是鏈表的隊列嘛郑什,對,就是這樣的蒲肋,那有什么說的呢蘑拯?首先說一下鎖的問題,這個隊列就很理智(233)的使用了兩個final的lock(take和put)兜粘,對嘛申窘,進出分開嘛,當年我在化工廠里面看設計圖的時候孔轴,就看到人家設計師說剃法,人流物流進進出出嚴格分開滴,沒錯距糖,程序就是要反應現(xiàn)實的玄窝,不然寫程序不就找不到使用的價值了(繼續(xù)瞎說)。然后再來一點不瞎說的悍引。既然是鏈表恩脂,我們存取是不是可以使用雙向鏈表尾放頭取呢?可以趣斤,沒錯俩块,人家就是這么玩的,是不是一點都不意外浓领,你能想到的這么簡單的東西玉凯,人家大牛早就實現(xiàn)了。那么联贩,這樣是不是就有一個很熟悉的生產(chǎn)者消費者模式出現(xiàn)了漫仆,哇塞,如果你能想到這里泪幌,那么和我這個渣渣同一水準了呢(2333)盲厌,反正寫東西的時候署照,瞎想一下,萬一和大神想到一起去了呢吗浩。對建芙,這里就是這樣的。通過前面的兩把鎖(顯示鎖aqs實現(xiàn)的ReentrantLock懂扼,我打算下一次寫一下這個)禁荸,獲取condition(用來休息的),然后每次存/取的時候阀湿,檢查一下容量赶熟,如果滿/虧,那么就休息一下炕倘。至于為什么是循環(huán)钧大,那是因為這里有并發(fā),每次醒來的時候罩旋,先循環(huán)一下啊央,能不能拿到,拿不到就繼續(xù)休息涨醋。如果拿到了瓜饥,就去干活吧。take和put最后一行代碼是叫醒對應的線程浴骂,這個是因為這個線程已經(jīng)拿走了一個乓土,所以讓等著的put填進來。(這里我專門提一下的原因是我第一次就看錯了溯警,搞了半天才發(fā)現(xiàn)是自己atomicInteger的方法getAndDecrement搞錯了---拿到舊值(我理解成了拿到增加之后的值了))趣苏,所以這里我專門提示一下。然后這個隊列就沒有太多可以說的了梯轻,而且實現(xiàn)也很簡單食磕,看一遍源碼就知道了。
? ? ? ? 剩下一個是SynchronousQueue喳挑,這個沒有加blocking彬伦,是不是以為就不是阻塞的呢?這個隊列很特殊伊诵,它是一個轉(zhuǎn)移隊列单绑,自己不保存東西,也就是容量是0,曹宴。那他怎么玩的呢搂橙?首先通過內(nèi)部transfer對象來實現(xiàn)(queue公平,stack非公平)隊列笛坦。說好的不保存東西的呢份氧,是的唯袄,任務過來了弯屈,它是不會保存的蜗帜。那么他保存的是什么呢,沒錯资厉,是空閑的線程厅缺。這個隊列通過put的東西會一直阻塞自己,只有當下一個線程過來take之后才會繼續(xù)自己的邏輯宴偿。所以當新任務來的時候湘捎,這個隊列直接創(chuàng)建新的線程去執(zhí)行,自己不緩存任務窄刘。而當空閑線程回來的時候窥妇,那就可以進隊了,因為put就是await當前線程的娩践,這個是通過lockResource也就是底層native方法的wait活翩,因為這個隊列沒有使用aqs,直接是使用的cas算法翻伺,代碼那叫一個復雜呀材泄。不過慢慢也能看懂,就是node的進出隊列吨岭。算是一個優(yōu)化鎖的實現(xiàn)拉宗,先自旋一會然后等待(根據(jù)cpu數(shù)定次數(shù),如果單cpu辣辫,就不要自旋了旦事,直接wait吧,其他的就是需要的)急灭,這個設計就非常合理姐浮,因為單cpu如果大量的自旋,會明顯拖慢其他的線程化戳。至于這個隊列的公平模式和非公平模式单料,我就不細說了,感覺和鎖的機制差不多点楼,只是他使用了兩種數(shù)據(jù)結(jié)構(gòu)罷了扫尖。
? ? ? ? 說了這么多廢話,現(xiàn)在再說一下java的推薦的幾種選擇掠廓,單線程和固定容量的線程都是選擇的是LinkedBlockingQueue换怖,沒有大小上限,默認是integer.MAX_VALUE(當然可以設置大小的)蟀瞧,前面我說的不建議使用拒絕措施就是這里看到的沉颂,因為如果核心池和最大池固定了条摸,就不再固定緩存的數(shù)量了,這樣在正常的情況就不會有什么拒絕的機會了铸屉。至于cached的線程池钉蒲,那還用說,雖然沒有核心線程彻坛,但是我們最大線程多呀顷啼,也是integer.MAX_VALUE(其實線程池沒有這么大的容量,因為線程池容量和狀態(tài)是一個integer昌屉,32位钙蒙,高位3個位置保存status,低位29個位置保存容量间驮,這種設計躬厌,省了一個atmicinteger的對象,但是對于我這種渣渣竞帽,看起源碼來扛施,苦的一批),所以抢呆,這個緩存池也沒有機會拒絕煮嫌,對了,差點忘記說了抱虐,這個隊列使用的就是SynchronousQueue昌阿,每次過來任務也不緩存了,直接創(chuàng)建新線程就可以了恳邀,反正我們線程多懦冰,用來了過期時間到了也自動銷毀了。不過這種方法其實不是很好谣沸,因為對于任務時間長的任務刷钢,會出現(xiàn)大量的運行和就緒的線程,導致其他的線程運行速度受限乳附,所以最好使用這個線程池的時候内地,執(zhí)行的都是小任務。
? ? ? ? 好了赋除,線程池就到這里吧阱缓,其實很多線程池的東西可以寫,但是寫起來真的沒完沒了了举农,比如每一個線程的創(chuàng)建都是一個work荆针,work不單單實現(xiàn)了runnable接口,還繼承了aqs,沒錯航背,每一個任務本身就是一把鎖喉悴,而且是排他鎖。好了玖媚,大致就這么多了箕肃,下一篇,我們聊聊鎖吧最盅。
? ??????