提綱
是什么(使用線程池的原因刺下,線程池的定義坝初,好處,線程池原理)
怎么用(常見的使用方式零渐,以及各個(gè)參數(shù)的作用)
為什么(源碼分析,設(shè)計(jì)模式分析)
關(guān)于原理在android中的部分應(yīng)用系忙,部分注意事項(xiàng)
引子(原因)
多線程技術(shù):
多線程技術(shù)主要解決處理器單元內(nèi)多個(gè)線程執(zhí)行的問題诵盼,它可以顯著減少處理器單元的閑置時(shí)間,增加處理器單元的吞吐能力
多線程的異步執(zhí)行方式银还,雖然能夠最大限度發(fā)揮多核計(jì)算機(jī)的計(jì)算能力风宁,但是如果不加控制,反而會(huì)對(duì)系統(tǒng)造成負(fù)擔(dān)蛹疯。線程本身也要占用內(nèi)存空間戒财,大量的線程會(huì)占用內(nèi)存資源并且可能會(huì)導(dǎo)致Out of Memory。即便沒有這樣的情況捺弦,大量的線程回收也會(huì)給GC帶來很大的壓力饮寞。
當(dāng)創(chuàng)建線程時(shí)間+銷毀線程時(shí)間遠(yuǎn)大于在線程中執(zhí)行任務(wù)的時(shí)間時(shí) 或者 需要多次調(diào)用多線程異步任務(wù)時(shí)孝扛,我們可以考慮線程池
什么是線程池
定義
一種線程使用模式,通過池化技術(shù)維護(hù)著多個(gè)線程骂际,對(duì)線程進(jìn)行統(tǒng)一管理和復(fù)用疗琉,避免了在處理短時(shí)間任務(wù)時(shí)創(chuàng)建與銷毀線程的代價(jià)冈欢。線程池不僅能夠保證內(nèi)核的充分利用歉铝,還能防止過分調(diào)度。
原理
池化技術(shù)凑耻,享元模式太示,對(duì)線程進(jìn)行統(tǒng)一管理和復(fù)用
使用線程池的好處
減少了創(chuàng)建和銷毀線程的次數(shù),每個(gè)工作線程都可以被重復(fù)利用香浩,可執(zhí)行多個(gè)任務(wù)类缤。從而減少線程的創(chuàng)建和銷毀,節(jié)約系統(tǒng)的開銷
對(duì)線程具有統(tǒng)一的管理邻吭,可以根據(jù)系統(tǒng)的承受能力餐弱,調(diào)整線程池中工作線線程的數(shù)目,防止因?yàn)橄倪^多的內(nèi)存
簡(jiǎn)單的demo對(duì)比結(jié)果
怎么使用線程池
使用線程池主要涉及兩個(gè)類
ExecutorService:線程池接口囱晴,提供了眾多接口api來控制線程池中的線程
ThreadPoolExecutor:實(shí)現(xiàn)了ExecutorService接口膏蚓,并封裝了一系列的api使得它具有線程池的特性,其中包括工作隊(duì)列畸写、核心線程數(shù)驮瞧、最大線程數(shù)等,可以說這個(gè)就是線程池的代表類
要?jiǎng)?chuàng)建一個(gè)線程池只需要new ThreadPoolExecutor(…);就可以創(chuàng)建一個(gè)線程池枯芬,而如果這樣創(chuàng)建線程池的話论笔,我們需要配置一堆東西,非常麻煩千所,我們可以看一下它的構(gòu)造方法就知道了:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {...}
所以狂魔,官方也不推薦使用這種方法來創(chuàng)建線程池,而是推薦使用Executors的工廠方法來創(chuàng)建線程池淫痰,Executors類是官方提供的一個(gè)工廠類最楷,它里面封裝好了眾多功能不一樣的線程池,從而使得我們創(chuàng)建線程池非常的簡(jiǎn)便黑界,主要提供了如下五種功能不一樣的線程池:
1管嬉、newFixedThreadPool() :
作用:該方法返回一個(gè)固定線程數(shù)量的線程池,該線程池中的線程數(shù)量始終不變朗鸠,即不會(huì)再創(chuàng)建新的線程蚯撩,也不會(huì)銷毀已經(jīng)創(chuàng)建好的線程,自始自終都是那幾個(gè)固定的線程在工作烛占,所以該線程池可以控制線程的最大并發(fā)數(shù)胎挎。
栗子:假如有一個(gè)新任務(wù)提交時(shí)沟启,線程池中如果有空閑的線程則立即使用空閑線程來處理任務(wù),如果沒有犹菇,則會(huì)把這個(gè)新任務(wù)存在一個(gè)任務(wù)隊(duì)列中德迹,一旦有線程空閑了,則按FIFO方式處理任務(wù)隊(duì)列中的任務(wù)揭芍。
2胳搞、newCachedThreadPool() :
作用:該方法返回一個(gè)可以根據(jù)實(shí)際情況調(diào)整線程池中線程的數(shù)量的線程池。即該線程池中的線程數(shù)量不確定称杨,是根據(jù)實(shí)際情況動(dòng)態(tài)調(diào)整的肌毅。
栗子:假如該線程池中的所有線程都正在工作,而此時(shí)有新任務(wù)提交姑原,那么將會(huì)創(chuàng)建新的線程去處理該任務(wù)悬而,而此時(shí)假如之前有一些線程完成了任務(wù),現(xiàn)在又有新任務(wù)提交锭汛,那么將不會(huì)創(chuàng)建新線程去處理笨奠,而是復(fù)用空閑的線程去處理新任務(wù)。那么此時(shí)有人有疑問了唤殴,那這樣來說該線程池的線程豈不是會(huì)越集越多般婆?其實(shí)并不會(huì),因?yàn)榫€程池中的線程都有一個(gè)“保持活動(dòng)時(shí)間”的參數(shù)眨八,通過配置它腺兴,如果線程池中的空閑線程的空閑時(shí)間超過該“保存活動(dòng)時(shí)間”則立刻停止該線程,而該線程池默認(rèn)的“保持活動(dòng)時(shí)間”為60s廉侧。
3页响、newSingleThreadExecutor() :
作用:該方法返回一個(gè)只有一個(gè)線程的線程池,即每次只能執(zhí)行一個(gè)線程任務(wù)段誊,多余的任務(wù)會(huì)保存到一個(gè)任務(wù)隊(duì)列中闰蚕,等待這一個(gè)線程空閑,當(dāng)這個(gè)線程空閑了再按FIFO方式順序執(zhí)行任務(wù)隊(duì)列中的任務(wù)连舍。
4没陡、newScheduledThreadPool() :
作用:該方法返回一個(gè)可以控制線程池內(nèi)線程定時(shí)或周期性執(zhí)行某任務(wù)的線程池。
5索赏、newSingleThreadScheduledExecutor() :
作用:該方法返回一個(gè)可以控制線程池內(nèi)線程定時(shí)或周期性執(zhí)行某任務(wù)的線程池盼玄。只不過和上面的區(qū)別是該線程池大小為1,而上面的可以指定線程池的大小潜腻。
獲取這五種線程池
通過Executors的工廠方法來獲劝6:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); ? ?
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); ? ?
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); ? ?
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); ?
ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();
我們可以看到通過Executors的工廠方法來創(chuàng)建線程池極其簡(jiǎn)便,其實(shí)它的內(nèi)部還是通過new ThreadPoolExecutor(…)的方式創(chuàng)建線程池的融涣,我們看一下這些工廠方法的內(nèi)部實(shí)現(xiàn):
public static ExecutorService newFixedThreadPool(int nThreads) { ? ? ? ?
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); ? ? } ??
? public static ExecutorService newSingleThreadExecutor() {? ?
??return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue())); ?? }?
public static ExecutorService newCachedThreadPool() {? ? ?
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? 60L, TimeUnit.SECONDS,new SynchronousQueue()); ? ? }
我們可以清楚的看到這些方法的內(nèi)部實(shí)現(xiàn)都是通過創(chuàng)建一個(gè)ThreadPoolExecutor對(duì)象來創(chuàng)建的童番,正所謂萬變不離其宗精钮,所以我們要了解線程池還是得了解ThreadPoolExecutor這個(gè)線程池類
了解ThreadPoolExecutor
所以我們主要就是要了解ThreadPoolExecutor,從構(gòu)造方法開始:
我們可以看到它構(gòu)造方法的參數(shù)比較多剃斧,有七個(gè)轨香,下面一一來說明這些參數(shù)的作用:
corePoolSize:線程池中的核心線程數(shù)量
maximumPoolSize:線程池中的最大線程數(shù)量
keepAliveTime:這個(gè)就是上面說到的“保持活動(dòng)時(shí)間“,上面只是大概說明了一下它的作用幼东,不過它起作用必須在一個(gè)前提下臂容,就是當(dāng)線程池中的線程數(shù)量超過了corePoolSize時(shí),它表示多余的空閑線程的存活時(shí)間筋粗,即:多余的空閑線程在超過keepAliveTime時(shí)間內(nèi)沒有任務(wù)的話則被銷毀策橘。而這個(gè)主要應(yīng)用在緩存線程池中
unit:它是一個(gè)枚舉類型,表示keepAliveTime的單位娜亿,常用的如:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)
workQueue:任務(wù)隊(duì)列蚌堵,主要用來存儲(chǔ)已經(jīng)提交但未被執(zhí)行的任務(wù)买决,不同的線程池采用的排隊(duì)策略不一樣
threadFactory:線程工廠,用來創(chuàng)建線程池中的線程吼畏,通常用默認(rèn)的即可
handler:通常叫做拒絕策略督赤,1、在線程池已經(jīng)關(guān)閉的情況下 2泻蚊、任務(wù)太多導(dǎo)致最大線程數(shù)和任務(wù)隊(duì)列已經(jīng)飽和躲舌,無法再接收新的任務(wù) 。在上面兩種情況下性雄,只要滿足其中一種時(shí)没卸,在使用execute()來提交新的任務(wù)時(shí)將會(huì)拒絕,而默認(rèn)的拒絕策略是拋一個(gè)RejectedExecutionException異常
workQueue這個(gè)任務(wù)隊(duì)列卻要再次說明一下秒旋,它是一個(gè)BlockingQueue<Runnable>對(duì)象约计,而泛型則限定它是用來存放Runnable對(duì)象的,剛剛上面講了迁筛,不同的線程池它的任務(wù)隊(duì)列實(shí)現(xiàn)肯定是不一樣的煤蚌,所以,保證不同線程池有著不同的功能的核心就是這個(gè)workQueue的實(shí)現(xiàn)了细卧,細(xì)心的會(huì)發(fā)現(xiàn)在剛剛的用來創(chuàng)建線程池的工廠方法中尉桩,針對(duì)不同的線程池傳入的workQueue也不一樣,下面我總結(jié)一下這五種線程池分別用的是什么BlockingQueue:
1贪庙、newFixedThreadPool()—>LinkedBlockingQueue
2蜘犁、newSingleThreadExecutor()—>LinkedBlockingQueue
3、newCachedThreadPool()—>SynchronousQueue
4插勤、newScheduledThreadPool()—>DelayedWorkQueue
5沽瘦、newSingleThreadScheduledExecutor()—>DelayedWorkQueue
這些隊(duì)列分別表示:
LinkedBlockingQueue:無界的隊(duì)列
SynchronousQueue:直接提交的隊(duì)列
DelayedWorkQueue:等待隊(duì)列
當(dāng)然實(shí)現(xiàn)了BlockingQueue接口的隊(duì)列還有:ArrayBlockingQueue(有界的隊(duì)列)革骨、PriorityBlockingQueue(優(yōu)先級(jí)隊(duì)列)。這些隊(duì)列的詳細(xì)作用就不多介紹了析恋。
當(dāng) Executor 已經(jīng)關(guān)閉良哲,并且 Executor 將有限邊界用于最大線程和工作隊(duì)列容量,且已經(jīng)飽和時(shí)助隧,在方法execute(java.lang.Runnable) 中提交的新任務(wù)將被拒絕筑凫。在以上兩種情況下,execute 方法都將調(diào)用其RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法并村。下面提供了四種預(yù)定義的處理程序策略:
A. 在默認(rèn)的 ThreadPoolExecutor.AbortPolicy 中巍实,處理程序遭到拒絕將拋出運(yùn)行時(shí) RejectedExecutionException。
B. 在 ThreadPoolExecutor.CallerRunsPolicy 中哩牍,線程調(diào)用運(yùn)行該任務(wù)的 execute 本身棚潦。此策略提供簡(jiǎn)單的反饋控制機(jī)制,能夠減緩新任務(wù)的提交速度膝昆。
C. 在 ThreadPoolExecutor.DiscardPolicy 中丸边,不能執(zhí)行的任務(wù)將被刪除。
D. 在 ThreadPoolExecutor.DiscardOldestPolicy 中荚孵,如果執(zhí)行程序尚未關(guān)閉妹窖,則位于工作隊(duì)列頭部的任務(wù)將被刪除,然后重試執(zhí)行程序(如果再次失敗收叶,則重復(fù)此過程)骄呼。
LinkedBlockingQueue
1:如果未指定容量,默認(rèn)容量為Integer.MAX_VALUE 判没,容量范圍可以在構(gòu)造方法參數(shù)中指定作為防止隊(duì)列過度擴(kuò)展蜓萄。
2:此對(duì)象是 線程阻塞-安全的
3:不接受 null 元素
4:它實(shí)現(xiàn)了BlockingQueue接口。
5:實(shí)現(xiàn)了 Collection 和 Iterator 接口的所有可選 方法哆致。
線程池ThreadPoolExecutor的使用
使用線程池绕德,其中涉及到一個(gè)極其重要的方法,即:
execute(Runnable command)
該方法意為執(zhí)行給定的任務(wù)摊阀,該任務(wù)處理可能在新的線程耻蛇、已入池的線程或者正調(diào)用的線程,這由ThreadPoolExecutor的實(shí)現(xiàn)決定胞此。
當(dāng)一個(gè)任務(wù)通過execute(Runnable)方法欲添加到線程池時(shí):
l 如果此時(shí)線程池中的數(shù)量小于corePoolSize臣咖,即使線程池中的線程都處于空閑狀態(tài),也要?jiǎng)?chuàng)建新的線程來處理被添加的任務(wù)漱牵。
l 如果此時(shí)線程池中的數(shù)量等于 corePoolSize夺蛇,但是緩沖隊(duì)列 workQueue未滿,那么任務(wù)被放入緩沖隊(duì)列酣胀。
l 如果此時(shí)線程池中的數(shù)量大于corePoolSize刁赦,緩沖隊(duì)列workQueue滿娶聘,并且線程池中的數(shù)量小于maximumPoolSize,建新的線程來處理被添加的任務(wù)甚脉。
l 如果此時(shí)線程池中的數(shù)量大于corePoolSize丸升,緩沖隊(duì)列workQueue滿,并且線程池中的數(shù)量等于maximumPoolSize牺氨,那么通過 handler所指定的策略來處理此任務(wù)狡耻。也就是:處理任務(wù)的優(yōu)先級(jí)為:核心線程corePoolSize、任務(wù)隊(duì)列workQueue猴凹、最大線程maximumPoolSize夷狰,如果三者都滿了,使用handler處理被拒絕的任務(wù)郊霎。
l 當(dāng)線程池中的線程數(shù)量大于 corePoolSize時(shí)沼头,如果某線程空閑時(shí)間超過keepAliveTime,線程將被終止歹篓。這樣瘫证,線程池可以動(dòng)態(tài)的調(diào)整池中的線程數(shù)。
優(yōu)先級(jí)線程池的優(yōu)點(diǎn)
優(yōu)先級(jí)可以在線程實(shí)現(xiàn)了BlockingQueue接口的隊(duì)列還有:ArrayBlockingQueue(有界的隊(duì)列)庄撮、PriorityBlockingQueue(優(yōu)先級(jí)隊(duì)列)。這些隊(duì)列的詳細(xì)作用就不多介紹了毙籽。池中線程數(shù)量不足或系統(tǒng)資源緊張時(shí)洞斯,優(yōu)先處理我們想要先處理的任務(wù),而優(yōu)先級(jí)低的則放到后面再處理坑赡,這極大改善了系統(tǒng)默認(rèn)線程池以FIFO方式處理任務(wù)的不靈活
擴(kuò)展線程池ThreadPoolExecutor
除了內(nèi)置的功能外烙如,ThreadPoolExecutor也向外提供了三個(gè)接口供我們自己擴(kuò)展?jié)M足我們需求的線程池,這三個(gè)接口分別是:
beforeExecute() - 任務(wù)執(zhí)行前執(zhí)行的方法
afterExecute() -任務(wù)執(zhí)行結(jié)束后執(zhí)行的方法
terminated() -線程池關(guān)閉后執(zhí)行的方法
這三個(gè)方法在ThreadPoolExecutor內(nèi)部都沒有實(shí)現(xiàn)
前面兩個(gè)方法我們可以在ThreadPoolExecutor內(nèi)部的runWorker()方法中找到毅否,而runWorker()是ThreadPoolExecutor的內(nèi)部類Worker實(shí)現(xiàn)的方法亚铁,Worker它實(shí)現(xiàn)了Runnable接口,也正是線程池內(nèi)處理任務(wù)的工作線程螟加,而Worker.runWorker()方法則是處理我們所提交的任務(wù)的方法徘溢,它會(huì)同時(shí)被多個(gè)線程訪問,所以我們看runWorker()方法的實(shí)現(xiàn)捆探,由于涉及到多個(gè)線程的異步調(diào)用然爆,必然是需要使用鎖來處理,而這里使用的是Lock來實(shí)現(xiàn)的黍图,我們來看看runWorker()方法內(nèi)主要實(shí)現(xiàn):
可以看到在task.run()之前和之后分別調(diào)用了beforeExecute和afterExecute方法曾雕,并傳入了我們的任務(wù)Runnable對(duì)象
而terminated()則是在關(guān)閉線程池的方法中調(diào)用,而關(guān)閉線程池有兩個(gè)方法助被,我貼其中一個(gè):
優(yōu)化線程池ThreadPoolExecutor
雖說線程池極大改善了系統(tǒng)的性能剖张,不過創(chuàng)建線程池也是需要資源的切诀,所以線程池內(nèi)線程數(shù)量的大小也會(huì)影響系統(tǒng)的性能,大了反而浪費(fèi)資源搔弄,小了反而影響系統(tǒng)的吞吐量幅虑,所以我們創(chuàng)建線程池需要把握一個(gè)度才能合理的發(fā)揮它的優(yōu)點(diǎn),通常來說我們要考慮的因素有CPU的數(shù)量肯污、內(nèi)存的大小翘单、并發(fā)請(qǐng)求的數(shù)量等因素,按需調(diào)整蹦渣。
通常核心線程數(shù)可以設(shè)為CPU數(shù)量+1哄芜,而最大線程數(shù)可以設(shè)為CPU的數(shù)量*2+1。
獲取CPU數(shù)量的方法為:
Runtime.getRuntime().availableProcessors();
java線程池大小為何會(huì)大多被設(shè)置成CPU核心數(shù)+1柬唯?https://blog.csdn.net/varyall/article/details/79583036
線程池大小設(shè)置认臊,CPU的核心數(shù)、線程數(shù)的關(guān)系和區(qū)別锄奢,同步與堵塞完全是兩碼事https://blog.csdn.net/tbdp6411/article/details/78443732
最佳線程數(shù)目 = (線程等待時(shí)間與線程CPU時(shí)間之比 + 1)* CPU數(shù)目
從任務(wù)的優(yōu)先級(jí)失晴,任務(wù)的執(zhí)行時(shí)間長(zhǎng)短,任務(wù)的性質(zhì)(CPU密集/ IO密集)拘央,任務(wù)的依賴關(guān)系這四個(gè)角度來分析涂屁。并且近可能地使用有界的工作隊(duì)列。
性質(zhì)不同的任務(wù)可用使用不同規(guī)模的線程池分開處理:
CPU密集型:盡可能少的線程灰伟,Ncpu+1
IO密集型:盡可能多的線程, Ncpu*2拆又,比如數(shù)據(jù)庫連接池
混合型:CPU密集型的任務(wù)與IO密集型任務(wù)的執(zhí)行時(shí)間差別較小,拆分為兩個(gè)線程池栏账;否則沒有必要拆分帖族。
shutdown()和shutdownNow()的區(qū)別
關(guān)于線程池的停止,ExecutorService為我們提供了兩個(gè)方法:shutdown和shutdownNow挡爵,這兩個(gè)方法各有不同竖般,可以根據(jù)實(shí)際需求方便的運(yùn)用,如下:
1茶鹃、shutdown()方法在終止前允許執(zhí)行以前提交的任務(wù)涣雕。
?2、shutdownNow()方法則是阻止正在任務(wù)隊(duì)列中等待任務(wù)的啟動(dòng)并試圖停止當(dāng)前正在執(zhí)行的任務(wù)前计。
遍歷線程池中的所有線程胞谭,然后逐個(gè)調(diào)用線程的interrupt方法來中斷線程.
shutdown 將線程池里的線程狀態(tài)設(shè)置成SHUTDOWN狀態(tài), 然后中斷所有沒有正在執(zhí)行任務(wù)的線程. shutdownNow 將線程池里的線程狀態(tài)設(shè)置成STOP狀態(tài), 然后停止所有正在執(zhí)行或暫停任務(wù)的線程. 只要調(diào)用這兩個(gè)關(guān)閉方法中的任意一個(gè), isShutDown() 返回true. 當(dāng)所有任務(wù)都成功關(guān)閉了, isTerminated()返回true.
線程池源碼分析原理
之前說到線程池原理是對(duì)多個(gè)線程的管理復(fù)用,減少了線程的創(chuàng)建和銷毀過程男杈,也減少了創(chuàng)建線程的數(shù)目丈屹,從而提高了效率。也講到各個(gè)參數(shù)的作用,接下來我們看看線程池的源碼是怎么實(shí)現(xiàn)這些操作的旺垒。
其中AtomicInteger變量ctl的功能非常強(qiáng)大:利用低29位表示線程池中線程數(shù)彩库,通過高3位表示線程池的運(yùn)行狀態(tài):
1、RUNNING:-1 << COUNT_BITS先蒋,即高3位為111骇钦,該狀態(tài)的線程池會(huì)接收新任務(wù),并處理阻塞隊(duì)列中的任務(wù)竞漾;?
2眯搭、SHUTDOWN: 0 << COUNT_BITS,即高3位為000业岁,該狀態(tài)的線程池不會(huì)接收新任務(wù)鳞仙,但會(huì)處理阻塞隊(duì)列中的任務(wù);?
3笔时、STOP : 1 << COUNT_BITS棍好,即高3位為001,該狀態(tài)的線程不會(huì)接收新任務(wù)允耿,也不會(huì)處理阻塞隊(duì)列中的任務(wù)借笙,而且會(huì)中斷正在運(yùn)行的任務(wù);?
4较锡、TIDYING : 2 << COUNT_BITS业稼,即高3位為010, 所有的任務(wù)都已經(jīng)終止
5、TERMINATED: 3 << COUNT_BITS蚂蕴,即高3位為011, terminated()方法已經(jīng)執(zhí)行完成
1盼忌、先看一下線程池的executor方法(任務(wù)入口)
判斷當(dāng)前活躍線程數(shù)是否小于corePoolSize,如果小于,則調(diào)用addWorker創(chuàng)建線程執(zhí)行任務(wù)
如果不小于corePoolSize掂墓,則將任務(wù)添加到workQueue隊(duì)列。
如果放入workQueue失敗看成,則創(chuàng)建線程執(zhí)行任務(wù)君编,如果這時(shí)創(chuàng)建線程失敗(當(dāng)前線程數(shù)不小于maximumPoolSize時(shí)),就會(huì)調(diào)用reject(內(nèi)部調(diào)用handler)拒絕接受任務(wù)川慌。
為什么需要double check線程池的狀態(tài)吃嘿?
在多線程環(huán)境下,線程池的狀態(tài)時(shí)刻在變化梦重,而ctl.get()是非原子操作兑燥,很有可能剛獲取了線程池狀態(tài)后線程池狀態(tài)就改變了。判斷是否將command加入workque是線程池之前的狀態(tài)琴拧。倘若沒有double check降瞳,萬一線程池處于非running狀態(tài)(在多線程環(huán)境下很有可能發(fā)生),那么command永遠(yuǎn)不會(huì)執(zhí)行。
2挣饥、再看下addWorker的方法實(shí)現(xiàn)
3汛聚、再到Worker里看看其實(shí)現(xiàn)
4、接下來咱們看看runWorker方法的邏輯
5短荐、最后在看看getTask方法實(shí)現(xiàn)
參考:Java線程池實(shí)現(xiàn)原理與源碼解析(jdk1.8)https://blog.csdn.net/programmer_at/article/details/79799267#4-threadpoolexecutor%E6%BA%90%E7%A0%81
關(guān)于原理在android中的部分應(yīng)用
關(guān)于AsyncTask的實(shí)現(xiàn)
大家都知道AsyncTask內(nèi)部實(shí)現(xiàn)其實(shí)就是Thread+Handler忍宋。其中Handler是為了處理線程之間的通信痕貌,而這個(gè)Thread到底是指什么呢?通過AsyncTask源碼可以得知讶踪,其實(shí)這個(gè)Thread是線程池芯侥,AsyncTask內(nèi)部實(shí)現(xiàn)了兩個(gè)線程池,分別是:串行線程池和固定線程數(shù)量的線程池乳讥。而這個(gè)固定線程數(shù)量則是通過CPU的數(shù)量決定的柱查。
在默認(rèn)情況下,我們大都通過AsyncTask::execute()來執(zhí)行任務(wù)的云石, 唉工,而execute()內(nèi)部則是調(diào)用executeOnExecutor(sDefaultExecutor, params)方法執(zhí)行的,第一個(gè)參數(shù)就是指定處理該任務(wù)的線程池汹忠,而默認(rèn)情況下AsyncTask是傳入串行線程池(在這里不講版本的變化)淋硝,也就是任務(wù)只能單個(gè)的按順序執(zhí)行,而我們要是想讓AsyncTask并行的處理任務(wù)宽菜,大家都知道調(diào)用AsyncTask::executeOnExecutor(sDefaultExecutor, params)方法傳入這個(gè)參數(shù)即可:AsyncTask.THREAD_POOL_EXECUTOR谣膳。 而這個(gè)參數(shù)的意義在于為任務(wù)指定了一個(gè)固定線程數(shù)量的線程池去處理,從而達(dá)到了并行處理的功能铅乡,我們可以在源碼中看到AsyncTask.THREAD_POOL_EXECUTOR這個(gè)參數(shù)就是一個(gè)固定線程數(shù)量的線程池:
關(guān)于message的實(shí)現(xiàn)
我們都知道m(xù)essage也有關(guān)于obtain的實(shí)現(xiàn) 继谚,通過obtain獲取message對(duì)象來使用代替new一個(gè)也是google推薦的做法, 這里也有用到享元模式阵幸,通過復(fù)用已有的message對(duì)象來減少創(chuàng)建及回收花履。
message內(nèi)部有個(gè)靜態(tài)的消息池,采用的是鏈表的實(shí)現(xiàn)方式進(jìn)行管理挚赊,最大數(shù)量為50诡壁,當(dāng)調(diào)用obtain方法時(shí),會(huì)先上鎖荠割,判斷當(dāng)前的節(jié)點(diǎn)是否為空妹卿,不為空則使用并將當(dāng)前節(jié)點(diǎn)指向下一個(gè),并減少poolsize。
對(duì)應(yīng)的纽帖,在釋放的時(shí)候宠漩,message為recycle中會(huì)上鎖后添加設(shè)置節(jié)點(diǎn),增加poolsize懊直,從而達(dá)成message的復(fù)用
關(guān)于對(duì)象池
在平時(shí)的邏輯中扒吁,如果我們想實(shí)現(xiàn)相關(guān)的享元復(fù)用,可以有多種方式室囊,只要形成相應(yīng)管理雕崩,用棧,隊(duì)列融撞,列表盼铁,數(shù)組,鏈表等結(jié)構(gòu)進(jìn)行管理尝偎,提供獲取和回收方法饶火。就可以達(dá)到相應(yīng)目的。
實(shí)際上致扯,在android的android.support.v4.util包中肤寝,就有提供Pools類來幫助我們事先緩存對(duì)象。提供了SimplePool抖僵、SynchronizedPool來創(chuàng)建對(duì)象池
在android.support.v4.util包下的Pools類中鲤看,分別聲明了Pool接口,SimplePool實(shí)現(xiàn)類與SynchronizedPool實(shí)現(xiàn)類耍群,其中具體的UML關(guān)系如下圖所示:
其內(nèi)部代碼也比較簡(jiǎn)單义桂,面向開發(fā)者提供了泛型的對(duì)象池,基于數(shù)組實(shí)現(xiàn)蹈垢,其中對(duì)象池的最大容量是通過用戶手動(dòng)設(shè)定慷吊。從對(duì)象池中獲取數(shù)據(jù)是通過acquire方法〔芴В回收當(dāng)前對(duì)象到對(duì)象池中是通過release方法罢浇。
關(guān)于acquire方法
在acquire方法中,會(huì)從對(duì)象池中取出對(duì)象沐祷。具體列子如下圖所示:
acquire()方法總會(huì)取當(dāng)前對(duì)象池中存儲(chǔ)的最后一個(gè)數(shù)據(jù)。如果有則返回攒岛。同時(shí)將該位置置為null赖临。反之返回為null。
關(guān)于release方法
在release方法中灾锯,會(huì)將對(duì)象緩存到對(duì)象池中兢榨。如果當(dāng)前對(duì)象已經(jīng)存在,會(huì)拋出異常。反之則存儲(chǔ)吵聪。具體列子如下圖所示:
release( T instance)方法凌那,總會(huì)將需要回收的對(duì)象存入當(dāng)前對(duì)象池中存儲(chǔ)的最后一個(gè)數(shù)據(jù)的下一個(gè)位置。如果當(dāng)前回收的對(duì)象已經(jīng)存在會(huì)拋出異常吟逝。反之則成功帽蝶。
同步對(duì)象池(SynchronizedPool)
SynchronizedPool的代碼理解起來也同樣非常簡(jiǎn)單,直接繼承SimplePool块攒。并重寫了SimplePool的兩個(gè)方法励稳。并為其加上了鎖,保證了多線程情況下使用的安全性囱井。
Synchronized是通過對(duì)象內(nèi)部的一個(gè)叫做監(jiān)視器鎖(monitor)來實(shí)現(xiàn)的驹尼。但是監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的Mutex Lock來實(shí)現(xiàn)的。而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換這就需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài),這個(gè)成本非常高,狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間肛鹏,這就是為什么Synchronized效率低的原因蓝丙。
因此,這種依賴于操作系統(tǒng)Mutex Lock所實(shí)現(xiàn)的鎖我們稱之為“重量級(jí)鎖”贾漏。JDK中對(duì)Synchronized做的種種優(yōu)化,其核心都是為了減少這種重量級(jí)鎖的使用。JDK1.6以后髓绽,為了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能妆绞,引入了“偏向鎖”和“輕量級(jí)鎖”顺呕。無鎖 --> 偏向鎖 --> 輕量級(jí) --> 重量級(jí)
鎖的方面有太多知識(shí),這里不會(huì)鋪開講括饶,有興趣可以研究下做分享
讓你徹底理解Synchronizedhttp://www.reibang.com/p/d53bf830fa09
分門別類總結(jié)Java中的各種鎖https://blog.csdn.net/renwei289443/article/details/79540809
JAVA鎖有哪些種類https://blog.csdn.net/nalanmingdian/article/details/77800355
簡(jiǎn)單了解鎖機(jī)制
概念:
鎖(lock)或互斥(mutex)是一種同步機(jī)制株茶,用于在有許多執(zhí)行線程的環(huán)境中強(qiáng)制對(duì)資源的訪問限制。鎖旨在強(qiáng)制實(shí)施互斥排他图焰、并發(fā)控制策略
Java線程阻塞:java的線程是映射到操作系統(tǒng)原生線程之上的启盛,如果要阻塞或喚醒一個(gè)線程就需要操作系統(tǒng)介入,需要在戶態(tài)與核心態(tài)之間切換技羔,這種切換會(huì)消耗大量的系統(tǒng)資源
?內(nèi)核態(tài):?CPU可以訪問內(nèi)存所有數(shù)據(jù), 包括外圍設(shè)備, 例如硬盤, 網(wǎng)卡. CPU也可以將自己從一個(gè)程序切換到另一個(gè)程序
?用戶態(tài):?只能受限的訪問內(nèi)存, 且不允許訪問外圍設(shè)備. 占用CPU的能力被剝奪, CPU資源可以被其他程序獲取
Java鎖的實(shí)現(xiàn):
markword是java對(duì)象數(shù)據(jù)結(jié)構(gòu)中的一部分僵闯,用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼(HashCode)藤滥、GC分代年齡鳖粟、鎖狀態(tài)標(biāo)志、線程持有的鎖拙绊、偏向線程ID向图、偏向時(shí)間戳等泳秀,其最后2bit是鎖狀態(tài)標(biāo)志位,表示不同級(jí)別的鎖
對(duì)鎖的不同效率進(jìn)行的分類(無鎖 --> 偏向鎖 --> 輕量級(jí) --> 重量級(jí))?
偏向鎖:指一段同步代碼一直被一個(gè)線程所訪問榄攀,那么該線程會(huì)自動(dòng)獲取鎖嗜傅。降低獲取鎖的代價(jià)。?
輕量級(jí)鎖:指當(dāng)鎖是偏向鎖的時(shí)候檩赢,被另一個(gè)線程所訪問吕嘀,偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖,其他線程會(huì)通過自旋的形式嘗試獲取鎖漠畜,不會(huì)阻塞币他,提高性能。
?重量級(jí)鎖: 指當(dāng)鎖為輕量級(jí)鎖的時(shí)候憔狞,另一個(gè)線程雖然是自旋蝴悉,但自旋不會(huì)一直持續(xù)下去,當(dāng)自旋一定次數(shù)的時(shí)候瘾敢,還沒有獲取到鎖拍冠,就會(huì)進(jìn)入阻塞,該鎖膨脹為重量級(jí)鎖簇抵。重量級(jí)鎖會(huì)讓其他申請(qǐng)的線程進(jìn)入阻塞庆杜,性能降低。
?synchronized的執(zhí)行過程:?
檢測(cè)Mark Word里面是不是當(dāng)前線程的ID碟摆,如果是晃财,表示當(dāng)前線程處于偏向鎖?
如果不是,則使用CAS將當(dāng)前線程的ID替換Mard Word典蜕,如果成功則表示當(dāng)前線程獲得偏向鎖断盛,置偏向標(biāo)志位1?
?如果失敗,則說明發(fā)生競(jìng)爭(zhēng)愉舔,撤銷偏向鎖钢猛,進(jìn)而升級(jí)為輕量級(jí)鎖。
?當(dāng)前線程使用CAS將對(duì)象頭的Mark Word替換為鎖記錄指針轩缤,如果成功命迈,當(dāng)前線程獲得鎖
?如果失敗,表示其他線程競(jìng)爭(zhēng)鎖火的,當(dāng)前線程便嘗試使用自旋來獲取鎖壶愤。
?如果自旋成功則依然處于輕量級(jí)狀態(tài)。?
?如果自旋失敗馏鹤,則升級(jí)為重量級(jí)鎖公你。
?Ps:?CAS:即CompareAndSwap縮寫,CAS需要有3個(gè)操作數(shù):內(nèi)存地址V假瞬,舊的預(yù)期值A(chǔ)陕靠,即將要更新的目標(biāo)值B。CAS指令執(zhí)行時(shí)脱茉,當(dāng)且僅當(dāng)內(nèi)存地址V的值與預(yù)期值A(chǔ)相等時(shí)剪芥,將內(nèi)存地址V的值修改為B,否則就什么都不做琴许。整個(gè)比較并替換的操作是一個(gè)原子操作税肪。(AtomicInteger)
自旋鎖:假設(shè)持有鎖的線程能在很短時(shí)間內(nèi)釋放鎖資源,那么那些等待競(jìng)爭(zhēng)鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進(jìn)入阻塞掛起狀態(tài)榜田,只需要自旋(如while循環(huán)空轉(zhuǎn))益兄,等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內(nèi)核的切換的消耗箭券。自旋是需要消耗cup的净捅,但不能一直占用cup自旋做無用功,所以需要設(shè)定一個(gè)自旋等待的最大時(shí)間?
此外鎖還具有多種分類和分類帶來的鎖名詞
?宏觀設(shè)計(jì)理念分類——樂觀鎖和悲觀鎖
?從其它等待中的線程是否按順序獲取鎖的角度劃分——公平鎖與非公平鎖
?從能否有多個(gè)線程持有同一把鎖的角度劃分——互斥鎖
?從一個(gè)線程能否遞歸獲取自己的鎖的角度劃分——重入鎖(遞歸鎖)
?從編譯器優(yōu)化的角度劃分——鎖消除和鎖粗化
?在不同的位置使用synchronized——類鎖和對(duì)象鎖
鎖優(yōu)化:
?減少鎖的持有時(shí)間:例如避免給整個(gè)方法加鎖
?減小鎖的粒度:將大對(duì)象辩块,拆成小對(duì)象蛔六,大大增加并行度,降低鎖競(jìng)爭(zhēng).如此一來偏向鎖废亭,輕量級(jí)鎖成功率提高.(代表類ConcurrentHashMap)
?讀寫分離鎖替代獨(dú)占鎖:讀-讀不互斥国章,讀-寫互斥,寫-寫互斥
?鎖分離:在讀寫鎖的思想上做進(jìn)一步的延伸,根據(jù)不同的功能拆分不同的鎖,進(jìn)行有效的鎖分離.(代表類LinkedBlockingQueue)
?鎖粗化:同一個(gè)鎖不停的進(jìn)行請(qǐng)求
同步和釋放, 其本身也會(huì)消耗系統(tǒng)寶貴的資源,反而不利于性能的優(yōu)化
?無鎖:鎖相比,使用CAS操作,由于其非阻塞性,因此不存在死鎖問題,同時(shí)線程之間的相互影響,?也遠(yuǎn)小于鎖的方式.使用無鎖的方案,可以減少鎖競(jìng)爭(zhēng)以及線程頻繁調(diào)度帶來的系統(tǒng)開銷.
關(guān)于線程的使用注意事項(xiàng)
在《阿里巴巴android開發(fā)手冊(cè)》有提到
關(guān)于線程池的使用注意
關(guān)于對(duì)象池復(fù)用豆村,在《effective java》書中也有提到
參考資料:
Java線程池實(shí)現(xiàn)原理與源碼解析(jdk1.8)https://blog.csdn.net/programmer_at/article/details/79799267#4-threadpoolexecutor%E6%BA%90%E7%A0%81
線程池的工作原理與源碼解讀https://www.cnblogs.com/qingquanzi/p/8146638.html