并發(fā)編程(二)-- 線程池

? ? ? ?了解了線程相關的知識,我們回到我們的系統(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詳解:? ? ? ?


創(chuàng)建線程池構造方法圖1
創(chuàng)建線程池構造方法圖2
創(chuàng)建線程池構造方法圖3

????主要參數(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方法。? ? ? ? ??

CallerRunsPolicy實現(xiàn)方法圖

? ? ? ? (4).CallerOldestPolicy: 如果線程池處于未關閉的狀態(tài)徒像,則獲取線程池中的隊列黍特,丟棄隊列最近的一個任務,并執(zhí)行當前任務锯蛀。

CallerOldestPolicy實現(xiàn)方法圖

?????????(5). 用戶可以自己實現(xiàn)RejectExecutionHandler 方法灭衷,然后在實例化ThreadPoolExecutor時,將其作為參數(shù)傳入旁涤。

源碼分析:

? ? 1. 在進行源碼分析之前翔曲,先看下ThreadPoolExecutor中幾個非常重要的靜態(tài)常量和靜態(tài)方法。

ThreadPoolExecutor常量和方法

? ? (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)亩码,代碼如下:

ThreadPoolExecutor入口executor()方法圖

????(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ī)避,讓其重新獲取內存值当犯。

addWorker圖1--增加工作線程數(shù)量圖

(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í)行異常策略惫叛。

addWorker圖2--啟動工作線程

? ? 4. 我們發(fā)現(xiàn)上述一直會判斷是否處于關閉狀態(tài),并且對關閉狀態(tài)做了一些處理尺碰,那么線程池是如何實現(xiàn)關閉的呢挣棕?

? ? (1)shutDown(): 關閉線程池狀態(tài),線程池不再接受任務亲桥, 并且嘗試中斷掉那些不處于運行中的任務洛心。

shutDown方法圖
interruptIdleWorker方法圖

? ? (2)shutDownNow(): 關閉線程池狀態(tài),線程池不再接受任務题篷,并且嘗試中斷掉存在的任務词身。與shutDown不同的是,它不管線程是不是處于運行中番枚,都會去進行中斷法严。shutDownNow()方法只是調用處理線程的方法不一樣,如下:不會獲取到Work對象的鎖葫笼,而是如果該線程未中斷深啤,就嘗試中斷。

shutdowNow方法調用的interruptWorkers圖

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(隊列同步器)都沒有做做介紹罢绽,之后寫下鎖和隊列同步器的相關知識畏线。? 最后,謝謝各位大佬的閱讀良价!? ??

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末寝殴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子棚壁,更是在濱河造成了極大的恐慌,老刑警劉巖栈虚,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袖外,死亡現(xiàn)場離奇詭異,居然都是意外死亡魂务,警方通過查閱死者的電腦和手機曼验,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門泌射,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鬓照,你說我怎么就攤上這事熔酷。” “怎么了豺裆?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵拒秘,是天一觀的道長。 經常有香客問我臭猜,道長躺酒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任蔑歌,我火速辦了婚禮羹应,結果婚禮上,老公的妹妹穿的比我還像新娘次屠。我一直安慰自己园匹,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布劫灶。 她就那樣靜靜地躺著裸违,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浑此。 梳的紋絲不亂的頭發(fā)上累颂,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音凛俱,去河邊找鬼紊馏。 笑死,一個胖子當著我的面吹牛蒲犬,可吹牛的內容都是我干的朱监。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼原叮,長吁一口氣:“原來是場噩夢啊……” “哼赫编!你這毒婦竟也來了?” 一聲冷哼從身側響起奋隶,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤擂送,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后唯欣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘹吨,經...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年境氢,在試婚紗的時候發(fā)現(xiàn)自己被綠了蟀拷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碰纬。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖问芬,靈堂內的尸體忽然破棺而出悦析,到底是詐尸還是另有隱情,我是刑警寧澤此衅,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布强戴,位于F島的核電站,受9級特大地震影響炕柔,放射性物質發(fā)生泄漏酌泰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一匕累、第九天 我趴在偏房一處隱蔽的房頂上張望陵刹。 院中可真熱鬧,春花似錦欢嘿、人聲如沸衰琐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽羡宙。三九已至,卻和暖如春掐隐,著一層夾襖步出監(jiān)牢的瞬間狗热,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工虑省, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留匿刮,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓探颈,卻偏偏與公主長得像熟丸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子伪节,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359