? ? ? ?了解了線程相關(guān)的知識,我們回到我們的系統(tǒng)開發(fā)中,怎么在實際的系統(tǒng)的開發(fā)中使用到線程,如果我們需要使用線程來實現(xiàn)群發(fā)郵件的效果,從消息隊列中取到郵件相關(guān)的信息,然后通過郵件發(fā)送服務(wù)器發(fā)送郵件。實現(xiàn)如下,當(dāng)有一個消息需要發(fā)送就需要new一個線程,那么當(dāng)系統(tǒng)中需要有幾千個或幾萬個郵件需要發(fā)送時,那么就會出現(xiàn)線程數(shù)量太多而造成資源消耗嚴(yán)重,甚至是難以想象的異常。那么怎么控制創(chuàng)建多個線程,怎么管理多個線程,Java并發(fā)編程提供了一個非常重要的類--線程池。
優(yōu)點:
? ? ? 1.降低資源消耗:線程池可以重復(fù)使用已創(chuàng)建的線程,降低了重新創(chuàng)建線程和銷毀線程的好處。
? ? ? 2.提高響應(yīng)速度:當(dāng)任務(wù)到達需要執(zhí)行時,任務(wù)不需要等待線程的創(chuàng)建而直接執(zhí)行任務(wù)
? ? ? ?3.提高線程的可靠性:使用線程池可以對任務(wù)做到統(tǒng)一分配,調(diào)優(yōu)和監(jiān)控。
關(guān)鍵類:
????1. Executor:? 線程池最頂層接口,只提供了execute(Runnable command)方法。之后的類都是在此在接口上進行擴展。
????2.ExecutorService: 對頂層接口擴展的接口,提供關(guān)閉(shutDown),立即關(guān)閉(shutDownNow),是否關(guān)閉(isShutDown),是否終止(isTerminated),提交Callable任務(wù)的抽象方法。
????3.AbstractExecutorService: 對ExecutorService接口的擴展,提供了newTaskFor(),主要是將Runnable、Callable的任務(wù)封裝為一個實現(xiàn)了Future和Runnable的對象,能夠?qū)崿F(xiàn)對結(jié)果的返回。
4. Executors :線程池提供的一個工具類,可以用于創(chuàng)建固定大小的線程池、緩存線程池、單線程線程池等。
? ? 后邊會詳細(xì)介紹剩余的兩個類:ThreadPoolExecutor和SchedulePoolExecutor
ThreadPoolExecutor詳解:? ? ? ?
????主要參數(shù):? ? ? ? ?
????????1. corePoolSize: 核心線程池線程數(shù)量,
? ? ? ? 2. maximumPoolSize: 最大線程數(shù)量,
? ? ? ? 3. keepAliveTime: 線程空閑時間
? ? ? ? 4.unit:空閑時間單位
? ? ? ? 5.workQueue: 工作隊列,主要有四種實現(xiàn)方式 。
? ? ? ? ? ? a.????ArrayBlockingQueue: 創(chuàng)建固定大小的阻塞隊列, 采用的是數(shù)組的結(jié)構(gòu)方式
? ? ? ? ? ? b.? ? LinkedBlockingQueue:? ? 創(chuàng)建固定大小的阻塞隊列,如果為傳入?yún)?shù),則會創(chuàng)建Integer.MaxValue大小的隊列
? ? ? ? ? ? c.? ??SynchronousQueue: 創(chuàng)建一個不存儲元素的阻塞隊列,每一個元素的插入都必須等待一個元素的移除操作,不然會一直阻塞。
? ? ? ? ? ? d.? ? PriorityBlockingQueue: 一個具有優(yōu)先級的無限阻塞隊列。
? ? ? ? 6. ThreadFactory:? ? ?用于設(shè)置線程的工廠,通過改類可以給提交的線程創(chuàng)建更有意義的名字。
實現(xiàn)原理:
????1.當(dāng)一個任務(wù)被提交時,線程池會判斷當(dāng)前核心線程是否已經(jīng)達到最大,如果沒有則會創(chuàng)建一個新的工作線程執(zhí)行任務(wù),該步驟會進行加鎖; 如果已滿,則會 執(zhí)行步驟2。
????2.嘗試將任務(wù)進入到阻塞隊列中,如果成功添加到阻塞隊列中,之后當(dāng)有工作線程執(zhí)行完畢之后就會從工作隊列中獲取一個任務(wù)繼續(xù)執(zhí)行;如果加入到阻塞隊列失敗,則會執(zhí)行步驟3。
????3.判斷運行的線程數(shù)量是否已達到最大的設(shè)置的線程數(shù),如果未達到,則創(chuàng)建新的工作線程來執(zhí)行任務(wù),該步驟會進行加鎖;如果已達到則執(zhí)行用戶設(shè)置的執(zhí)行策略,轉(zhuǎn)到步驟4。
? ? 4.線程池根據(jù)以下4種RejectExecutionHandle執(zhí)行策略來進行當(dāng)不能執(zhí)行任務(wù)時具體的做法。
????????(1). AbortPolicy: 實現(xiàn)了該接口,但是在實現(xiàn)的方法中直接拋出一個異常。默認(rèn)不執(zhí)行,直接返回異常。
????????(2).DiscardPolicy:? 實現(xiàn)方法沒有做任何操作。
????????(3).CallerRunsPolicy:? 判斷當(dāng)前線程池是否是未關(guān)閉的狀態(tài),如果是未關(guān)閉的狀態(tài)則直接調(diào)用任務(wù)的run方法。? ? ? ? ??
? ? ? ? (4).CallerOldestPolicy: 如果線程池處于未關(guān)閉的狀態(tài),則獲取線程池中的隊列,丟棄隊列最近的一個任務(wù),并執(zhí)行當(dāng)前任務(wù)。
?????????(5). 用戶可以自己實現(xiàn)RejectExecutionHandler 方法,然后在實例化ThreadPoolExecutor時,將其作為參數(shù)傳入。
源碼分析:
? ? 1. 在進行源碼分析之前,先看下ThreadPoolExecutor中幾個非常重要的靜態(tài)常量和靜態(tài)方法。
? ? (1) ctl: 一個在線程池整個運行過程中都非常重要的一個常量,高3位表示運行的狀態(tài),低29未表示線程池中運行的線程數(shù)量。
? ??(2)COUNT_BITS: 將Interger的大小32-3, 即為29。
????(3)CAPACITY: 將1左移29位,然后再減掉1,即低29位都為1.
? ? (4)RUNNING: 將-1轉(zhuǎn)換為2進制轉(zhuǎn)換位,然后將轉(zhuǎn)換后二進制為往左移動29位,移動后高3位即為110。
? ? (5)SHUTDOWN: 同(2),移動后高3位000。
? ??(6)STOP: 同(2),移動后高3位001。
? ? (7)TIDYING:?同(2),移動后高3位010。
? ? (8)TERMINATED: 同(2), 移動后高3位101。
? ? (9)runStateOf(int c): c為當(dāng)前的ctl值,將其和CAPACITY的值取反(即低29位為0,高3位為1),然后位與之后只即為當(dāng)前線程池的狀態(tài)。
? ? (10)workCountof(int c): c為當(dāng)前ctl的值, 將其和CAPACITY的值進行&之后,獲取的即是當(dāng)前線程池線程的數(shù)量。
2. 進入到ThreadPoolExecutor的入口方法executor(Runnable command),代碼如下:
????(1) 首先獲取當(dāng)前ctl的值,之前已經(jīng)說過ctl可以保存當(dāng)前線程池的狀態(tài)和線程數(shù)量。
? ? (2)判斷線程池中的數(shù)量是否小于核心線程數(shù),如果小于,則將當(dāng)前的任務(wù)加入到工作線程中。若不小于核心線程數(shù),則執(zhí)行步驟3.
? ? (3)判斷線程池的狀態(tài)是否處于正在運行中,如果處于的話則將當(dāng)前的任務(wù)加入到工作隊列中。我之前一直懷疑為什么在為什么在addWorker方法中會加鎖機制,而加入隊列時未加鎖機制,在看了BlockingQueue的源碼之后發(fā)現(xiàn),它的插入、刪除操作也使用重入鎖機制,這樣就保證了并發(fā)加入隊列的操作是安全的。若執(zhí)行加入隊列成功,則執(zhí)行步驟4,若不成功則執(zhí)行步驟6.
? ? (4)重新檢查當(dāng)前線程池的狀態(tài),如果當(dāng)前線程池的狀態(tài)是在運行中,是則移除之前加入的任務(wù),并執(zhí)行異常策略。默認(rèn)的是AbortPolicy。否則執(zhí)行步驟5.
? ? (5)如果當(dāng)前線程的數(shù)量是0,則表明當(dāng)前處于shutDown狀態(tài),并且線程池中沒有線程則運行,這時像工作線程中添加一個空的任務(wù)。
? ? (6)繼續(xù)像工作線程中添加任務(wù),此時傳入false表示創(chuàng)建的不是核心的線程。如果不成功,則執(zhí)行異常策略。
3. 上述execute 方法中多次調(diào)用了addWorker方法,接下來我們看下addWorker方法,代碼如下:
? ? (1)采用循環(huán)+CAS操作來更新工作線程的數(shù)量,首先獲取當(dāng)前的ctl值,然后獲取其狀態(tài)rs。
? ? (2)檢查當(dāng)前的狀態(tài),當(dāng)狀態(tài)滿足大于關(guān)閉狀態(tài)或者處于關(guān)閉狀態(tài),當(dāng)前任務(wù)為空以下條件則直接返回:
????(3)獲取當(dāng)前的工作線程的數(shù)量,如果工作的線程數(shù)量大于CAPACITY或創(chuàng)建核心線程大于配置的核心線程數(shù)或創(chuàng)建最大的線程數(shù)大于配置的最大線程數(shù)量則直接返回false.
? ? (4)使用CAS操作對工作線程數(shù)量+1,如果新增成功則跳出循環(huán),進入到下圖2;否則繼續(xù)執(zhí)行
? ? (5)獲取當(dāng)前的ctl值,判斷ctl和之前開始取的值是否一致,這里主要是為了防止在做新增線程數(shù)量時,ctl的內(nèi)存存儲的值已發(fā)生了改變,這樣做可以規(guī)避,讓其重新獲取內(nèi)存值。
(6)將當(dāng)前的任務(wù)封裝為一個Work,Work的主要包含firstTask和thread變量。firstWork即是我們提交的任務(wù)。
? ? (7)判斷線程是否為空,若不為null,則進行加鎖,這里是為了防止多個任務(wù)提交時,出現(xiàn)競爭導(dǎo)致works的大小不準(zhǔn)確。
? ? (8)獲取線程池狀態(tài),若是處于運行中或是處于關(guān)閉狀態(tài),判斷工作線程是否還存活,若存活,則拋出異常。
? ? (9)向集合中添加該Worker,并判斷當(dāng)前線程的大小是否大于線程池中出現(xiàn)最多線程數(shù)量,如果多余則替換,之后將啟動Worker的標(biāo)志設(shè)為true。
? ? ?(10)最后在finally方法中判斷啟動worker的標(biāo)志是否為true,若為true則啟動,并且釋放掉鎖。
? ? 至此向線程池提交任務(wù)的源碼基本就完成了,當(dāng)然如果啟動Worker的標(biāo)志為false,就會執(zhí)行addWorkfailed方法,將線程數(shù)量-1,并且執(zhí)行異常策略。
? ? 4. 我們發(fā)現(xiàn)上述一直會判斷是否處于關(guān)閉狀態(tài),并且對關(guān)閉狀態(tài)做了一些處理,那么線程池是如何實現(xiàn)關(guān)閉的呢?
? ? (1)shutDown(): 關(guān)閉線程池狀態(tài),線程池不再接受任務(wù), 并且嘗試中斷掉那些不處于運行中的任務(wù)。
? ? (2)shutDownNow(): 關(guān)閉線程池狀態(tài),線程池不再接受任務(wù),并且嘗試中斷掉存在的任務(wù)。與shutDown不同的是,它不管線程是不是處于運行中,都會去進行中斷。shutDownNow()方法只是調(diào)用處理線程的方法不一樣,如下:不會獲取到Work對象的鎖,而是如果該線程未中斷,就嘗試中斷。
Executors工具類:
? ? 1. newFixedThreadPool(int core):創(chuàng)建固定大小的線程池,其內(nèi)部實現(xiàn)調(diào)用了ThreadPoolExecutor(core, core, 0L, TimeUnit.MILLSECONDS,new LinkedBlockingQueue)的方法。即核心線程數(shù)和最大線程數(shù)想等,隊列使用無界隊列,這樣子線程池的核心線程不會超過最大的線程,等待的線程都在工作隊列上等待。若不調(diào)用shutDown()或shutDownNow()方法,則不會執(zhí)行異常策略。
? ? 2.newSingleThreadPollExecutor(): 創(chuàng)建一個只有一個工作線程的線程池,其內(nèi)部實際調(diào)用了ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLSECONDS,new LinkedBlockingQueue)。這個和固定線程池非常相似,固定線程池核心線程由傳入的參數(shù)決定,而單線程池則默認(rèn)核心線程數(shù)為1。使用無界隊列作為工作隊列,其效果和固定線程池一樣。
? ? 3. newCachedThreadPool: 創(chuàng)建一個最大線程數(shù)為int類型最大值的線程池,內(nèi)部調(diào)用了ThreadPoolExecutor(0, Interger.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchroncousQueue)方法。沒來一個任務(wù)則創(chuàng)建一個工作線程來進行運行,但是空閑線程的時間設(shè)置為60S ,如果空閑的線程超過60s,則會關(guān)閉該線程。該線程池采用了SynchroncousQueue作為工作隊列,隊列本身不存儲任務(wù)。
????4.newScheduledThreadPool(int nThreads): 創(chuàng)建一個線程數(shù)量為nThreads的線程池??梢匝舆t、周期線的執(zhí)行任務(wù)。
? ? 什么情況使用什么隊列?
? ? ? ? ? ?(1) 速度快,任務(wù)小,newCachedThreadPool沒有錯
? ? ? ? ? ? (2)任務(wù)重,消耗大,newFixedThreadPool頂呱呱
? ? ? ? ? ? (3)順序執(zhí)行用newSingleThreadPollExecutor
? ? ? ? ? ? (4)想延遲,有周期,那你就得用newScheduledThreadPool。
總結(jié):
? ? 線程池在實際的開發(fā)中起著至關(guān)重要的作用,本文也只是對其中的一些簡單的知識點和代碼實現(xiàn)做了一個講述,想代碼中使用到的鎖和AbstractSynchronousQueue(隊列同步器)都沒有做做介紹,之后寫下鎖和隊列同步器的相關(guān)知識。? 最后,謝謝各位大佬的閱讀!? ??