? ? ? ?了解了線程相關的知識,我們回到我們的系統(tǒng)開發(fā)中钳垮,怎么在實際的系統(tǒng)的開發(fā)中使用到線程焙糟,如果我們需要使用線程來實現(xiàn)群發(fā)郵件的效果,從消息隊列中取到郵件相關的信息晾捏,然后通過郵件發(fā)送服務器發(fā)送郵件蒿涎。實現(xiàn)如下,當有一個消息需要發(fā)送就需要new一個線程惦辛,那么當系統(tǒng)中需要有幾千個或幾萬個郵件需要發(fā)送時劳秋,那么就會出現(xiàn)線程數(shù)量太多而造成資源消耗嚴重,甚至是難以想象的異常。那么怎么控制創(chuàng)建多個線程俗批,怎么管理多個線程俗或,Java并發(fā)編程提供了一個非常重要的類--線程池。
優(yōu)點:
? ? ? 1.降低資源消耗:線程池可以重復使用已創(chuàng)建的線程岁忘,降低了重新創(chuàng)建線程和銷毀線程的好處辛慰。
? ? ? 2.提高響應速度:當任務到達需要執(zhí)行時,任務不需要等待線程的創(chuàng)建而直接執(zhí)行任務
? ? ? ?3.提高線程的可靠性:使用線程池可以對任務做到統(tǒng)一分配干像,調優(yōu)和監(jiān)控帅腌。
關鍵類:
????1. Executor:? 線程池最頂層接口,只提供了execute(Runnable command)方法麻汰。之后的類都是在此在接口上進行擴展速客。
????2.ExecutorService: 對頂層接口擴展的接口,提供關閉(shutDown)五鲫,立即關閉(shutDownNow)溺职,是否關閉(isShutDown),是否終止(isTerminated)位喂,提交Callable任務的抽象方法浪耘。
????3.AbstractExecutorService: 對ExecutorService接口的擴展,提供了newTaskFor()塑崖,主要是將Runnable七冲、Callable的任務封裝為一個實現(xiàn)了Future和Runnable的對象,能夠實現(xiàn)對結果的返回规婆。
4. Executors :線程池提供的一個工具類澜躺,可以用于創(chuàng)建固定大小的線程池、緩存線程池抒蚜、單線程線程池等掘鄙。
? ? 后邊會詳細介紹剩余的兩個類:ThreadPoolExecutor和SchedulePoolExecutor
ThreadPoolExecutor詳解:? ? ? ?
????主要參數(shù):? ? ? ? ?
????????1. corePoolSize: 核心線程池線程數(shù)量,
? ? ? ? 2. maximumPoolSize: 最大線程數(shù)量削锰,
? ? ? ? 3. keepAliveTime: 線程空閑時間
? ? ? ? 4.unit:空閑時間單位
? ? ? ? 5.workQueue: 工作隊列通铲,主要有四種實現(xiàn)方式 。
? ? ? ? ? ? a.????ArrayBlockingQueue: 創(chuàng)建固定大小的阻塞隊列器贩, 采用的是數(shù)組的結構方式
? ? ? ? ? ? b.? ? LinkedBlockingQueue:? ? 創(chuàng)建固定大小的阻塞隊列颅夺,如果為傳入參數(shù),則會創(chuàng)建Integer.MaxValue大小的隊列
? ? ? ? ? ? c.? ??SynchronousQueue: 創(chuàng)建一個不存儲元素的阻塞隊列蛹稍,每一個元素的插入都必須等待一個元素的移除操作吧黄,不然會一直阻塞。
? ? ? ? ? ? d.? ? PriorityBlockingQueue: 一個具有優(yōu)先級的無限阻塞隊列唆姐。
? ? ? ? 6. ThreadFactory:? ? ?用于設置線程的工廠拗慨,通過改類可以給提交的線程創(chuàng)建更有意義的名字。
實現(xiàn)原理:
????1.當一個任務被提交時,線程池會判斷當前核心線程是否已經達到最大赵抢,如果沒有則會創(chuàng)建一個新的工作線程執(zhí)行任務剧蹂,該步驟會進行加鎖; 如果已滿,則會 執(zhí)行步驟2烦却。
????2.嘗試將任務進入到阻塞隊列中宠叼,如果成功添加到阻塞隊列中,之后當有工作線程執(zhí)行完畢之后就會從工作隊列中獲取一個任務繼續(xù)執(zhí)行其爵;如果加入到阻塞隊列失敗冒冬,則會執(zhí)行步驟3。
????3.判斷運行的線程數(shù)量是否已達到最大的設置的線程數(shù)摩渺,如果未達到简烤,則創(chuàng)建新的工作線程來執(zhí)行任務,該步驟會進行加鎖摇幻;如果已達到則執(zhí)行用戶設置的執(zhí)行策略横侦,轉到步驟4。
? ? 4.線程池根據(jù)以下4種RejectExecutionHandle執(zhí)行策略來進行當不能執(zhí)行任務時具體的做法绰姻。
????????(1). AbortPolicy: 實現(xiàn)了該接口丈咐,但是在實現(xiàn)的方法中直接拋出一個異常。默認不執(zhí)行龙宏,直接返回異常。
????????(2).DiscardPolicy:? 實現(xiàn)方法沒有做任何操作伤疙。
????????(3).CallerRunsPolicy:? 判斷當前線程池是否是未關閉的狀態(tài)银酗,如果是未關閉的狀態(tài)則直接調用任務的run方法。? ? ? ? ??
? ? ? ? (4).CallerOldestPolicy: 如果線程池處于未關閉的狀態(tài)徒像,則獲取線程池中的隊列黍特,丟棄隊列最近的一個任務,并執(zhí)行當前任務锯蛀。
?????????(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轉換為2進制轉換位猾蒂,然后將轉換后二進制為往左移動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為當前的ctl值,將其和CAPACITY的值取反(即低29位為0奢讨,高3位為1)稚叹,然后位與之后只即為當前線程池的狀態(tài)。
? ? (10)workCountof(int c): c為當前ctl的值拿诸, 將其和CAPACITY的值進行&之后扒袖,獲取的即是當前線程池線程的數(shù)量。
2. 進入到ThreadPoolExecutor的入口方法executor(Runnable command)亩码,代碼如下:
????(1) 首先獲取當前ctl的值季率,之前已經說過ctl可以保存當前線程池的狀態(tài)和線程數(shù)量。
? ? (2)判斷線程池中的數(shù)量是否小于核心線程數(shù)描沟,如果小于飒泻,則將當前的任務加入到工作線程中。若不小于核心線程數(shù)吏廉,則執(zhí)行步驟3.
? ? (3)判斷線程池的狀態(tài)是否處于正在運行中泞遗,如果處于的話則將當前的任務加入到工作隊列中。我之前一直懷疑為什么在為什么在addWorker方法中會加鎖機制席覆,而加入隊列時未加鎖機制史辙,在看了BlockingQueue的源碼之后發(fā)現(xiàn),它的插入佩伤、刪除操作也使用重入鎖機制聊倔,這樣就保證了并發(fā)加入隊列的操作是安全的。若執(zhí)行加入隊列成功生巡,則執(zhí)行步驟4方库,若不成功則執(zhí)行步驟6.
? ? (4)重新檢查當前線程池的狀態(tài),如果當前線程池的狀態(tài)是在運行中障斋,是則移除之前加入的任務纵潦,并執(zhí)行異常策略徐鹤。默認的是AbortPolicy。否則執(zhí)行步驟5.
? ? (5)如果當前線程的數(shù)量是0邀层,則表明當前處于shutDown狀態(tài)返敬,并且線程池中沒有線程則運行,這時像工作線程中添加一個空的任務寥院。
? ? (6)繼續(xù)像工作線程中添加任務劲赠,此時傳入false表示創(chuàng)建的不是核心的線程。如果不成功秸谢,則執(zhí)行異常策略凛澎。
3. 上述execute 方法中多次調用了addWorker方法,接下來我們看下addWorker方法估蹄,代碼如下:
? ? (1)采用循環(huán)+CAS操作來更新工作線程的數(shù)量塑煎,首先獲取當前的ctl值,然后獲取其狀態(tài)rs臭蚁。
? ? (2)檢查當前的狀態(tài)最铁,當狀態(tài)滿足大于關閉狀態(tài)或者處于關閉狀態(tài),當前任務為空以下條件則直接返回:
????(3)獲取當前的工作線程的數(shù)量垮兑,如果工作的線程數(shù)量大于CAPACITY或創(chuàng)建核心線程大于配置的核心線程數(shù)或創(chuàng)建最大的線程數(shù)大于配置的最大線程數(shù)量則直接返回false.
? ? (4)使用CAS操作對工作線程數(shù)量+1冷尉,如果新增成功則跳出循環(huán),進入到下圖2系枪;否則繼續(xù)執(zhí)行
? ? (5)獲取當前的ctl值雀哨,判斷ctl和之前開始取的值是否一致,這里主要是為了防止在做新增線程數(shù)量時私爷,ctl的內存存儲的值已發(fā)生了改變震束,這樣做可以規(guī)避,讓其重新獲取內存值当犯。
(6)將當前的任務封裝為一個Work,Work的主要包含firstTask和thread變量割疾。firstWork即是我們提交的任務嚎卫。
? ? (7)判斷線程是否為空,若不為null宏榕,則進行加鎖拓诸,這里是為了防止多個任務提交時,出現(xiàn)競爭導致works的大小不準確麻昼。
? ? (8)獲取線程池狀態(tài)奠支,若是處于運行中或是處于關閉狀態(tài),判斷工作線程是否還存活抚芦,若存活倍谜,則拋出異常迈螟。
? ? (9)向集合中添加該Worker,并判斷當前線程的大小是否大于線程池中出現(xiàn)最多線程數(shù)量尔崔,如果多余則替換答毫,之后將啟動Worker的標志設為true。
? ? ?(10)最后在finally方法中判斷啟動worker的標志是否為true季春,若為true則啟動洗搂,并且釋放掉鎖。
? ? 至此向線程池提交任務的源碼基本就完成了载弄,當然如果啟動Worker的標志為false耘拇,就會執(zhí)行addWorkfailed方法,將線程數(shù)量-1宇攻,并且執(zhí)行異常策略惫叛。
? ? 4. 我們發(fā)現(xiàn)上述一直會判斷是否處于關閉狀態(tài),并且對關閉狀態(tài)做了一些處理尺碰,那么線程池是如何實現(xiàn)關閉的呢挣棕?
? ? (1)shutDown(): 關閉線程池狀態(tài),線程池不再接受任務亲桥, 并且嘗試中斷掉那些不處于運行中的任務洛心。
? ? (2)shutDownNow(): 關閉線程池狀態(tài),線程池不再接受任務题篷,并且嘗試中斷掉存在的任務词身。與shutDown不同的是,它不管線程是不是處于運行中番枚,都會去進行中斷法严。shutDownNow()方法只是調用處理線程的方法不一樣,如下:不會獲取到Work對象的鎖葫笼,而是如果該線程未中斷深啤,就嘗試中斷。
Executors工具類:
? ? 1. newFixedThreadPool(int core):創(chuàng)建固定大小的線程池路星,其內部實現(xiàn)調用了ThreadPoolExecutor(core, core, 0L, TimeUnit.MILLSECONDS白指,new LinkedBlockingQueue)的方法鲁猩。即核心線程數(shù)和最大線程數(shù)想等,隊列使用無界隊列,這樣子線程池的核心線程不會超過最大的線程隆判,等待的線程都在工作隊列上等待粥惧。若不調用shutDown()或shutDownNow()方法屎篱,則不會執(zhí)行異常策略芋绸。
? ? 2.newSingleThreadPollExecutor(): 創(chuàng)建一個只有一個工作線程的線程池,其內部實際調用了ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLSECONDS,new LinkedBlockingQueue)迁客。這個和固定線程池非常相似郭宝,固定線程池核心線程由傳入的參數(shù)決定辞槐,而單線程池則默認核心線程數(shù)為1。使用無界隊列作為工作隊列剩蟀,其效果和固定線程池一樣催蝗。
? ? 3. newCachedThreadPool: 創(chuàng)建一個最大線程數(shù)為int類型最大值的線程池,內部調用了ThreadPoolExecutor(0, Interger.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchroncousQueue)方法育特。沒來一個任務則創(chuàng)建一個工作線程來進行運行丙号,但是空閑線程的時間設置為60S ,如果空閑的線程超過60s缰冤,則會關閉該線程犬缨。該線程池采用了SynchroncousQueue作為工作隊列,隊列本身不存儲任務棉浸。
????4.newScheduledThreadPool(int nThreads): 創(chuàng)建一個線程數(shù)量為nThreads的線程池怀薛。可以延遲迷郑、周期線的執(zhí)行任務枝恋。
? ? 什么情況使用什么隊列?
? ? ? ? ? ?(1) 速度快嗡害,任務小焚碌,newCachedThreadPool沒有錯
? ? ? ? ? ? (2)任務重,消耗大霸妹,newFixedThreadPool頂呱呱
? ? ? ? ? ? (3)順序執(zhí)行用newSingleThreadPollExecutor
? ? ? ? ? ? (4)想延遲十电,有周期,那你就得用newScheduledThreadPool叹螟。
總結:
? ? 線程池在實際的開發(fā)中起著至關重要的作用鹃骂,本文也只是對其中的一些簡單的知識點和代碼實現(xiàn)做了一個講述,想代碼中使用到的鎖和AbstractSynchronousQueue(隊列同步器)都沒有做做介紹罢绽,之后寫下鎖和隊列同步器的相關知識畏线。? 最后,謝謝各位大佬的閱讀良价!? ??