大战熟女丰满人妻av-荡女精品导航-岛国aaaa级午夜福利片-岛国av动作片在线观看-岛国av无码免费无禁网站-岛国大片激情做爰视频

專注Java教育14年 全國咨詢/投訴熱線:400-8080-105
動力節(jié)點LOGO圖
始于2009,口口相傳的Java黃埔軍校
首頁 學(xué)習(xí)攻略 Java學(xué)習(xí) Java教程:Java線程池的工作原理的分享

Java教程:Java線程池的工作原理的分享

更新時間:2019-08-30 14:28:18 來源:動力節(jié)點 瀏覽2408次

  我們在工作中或多或少都使用過線程池,但是為什么要使用線程池呢?從他的名字中我們就應(yīng)該知道,線程池使用了一種池化技術(shù),和很多其他池化技術(shù)一樣,都是為了更高效的利用資源,例如鏈接池,內(nèi)存池等等。


  數(shù)據(jù)庫鏈接是一種很昂貴的資源,創(chuàng)建和銷毀都需要付出高昂的代價,為了避免頻繁的創(chuàng)建數(shù)據(jù)庫鏈接,所以產(chǎn)生了鏈接池技術(shù)。優(yōu)先在池子中創(chuàng)建一批數(shù)據(jù)庫鏈接,有需要訪問數(shù)據(jù)庫時,直接到池子中去獲取一個可用的鏈接,使用完了之后再歸還到鏈接池中去。


  同樣的,線程也是一種寶貴的資源,并且也是一種有限的資源,創(chuàng)建和銷毀線程也同樣需要付出不菲的代價。我們所有的代碼都是由一個一個的線程支撐起來的,如今的芯片架構(gòu)也決定了我們必須編寫多線程執(zhí)行的程序,以獲取最高的程序性能。


  那么怎樣高效的管理多線程之間的分工與協(xié)作就成了一個關(guān)鍵問題,DougLea大神為我們設(shè)計并實現(xiàn)了一款線程池工具,通過該工具就可以實現(xiàn)多線程的能力,并實現(xiàn)任務(wù)的高效執(zhí)行與調(diào)度。


  為了正確合理的使用線程池工具,我們有必要對線程池的原理進(jìn)行了解。


  本篇文章主要從三個方面來對線程池進(jìn)行分析:線程池狀態(tài)、重要屬性、工作流程。


  線程池狀態(tài)


  首先線程池是有狀態(tài)的,這些狀態(tài)標(biāo)識這線程池內(nèi)部的一些運行情況,線程池的開啟到關(guān)閉的過程就是線程池狀態(tài)的一個流轉(zhuǎn)的過程。


  線程池共有五種狀態(tài):

image.png

image.png

  重要屬性


  一個線程池的核心參數(shù)有很多,每個參數(shù)都有著特殊的作用,各個參數(shù)聚合在一起后將完成整個線程池的完整工作。


  1、線程狀態(tài)和工作線程數(shù)量


  首先線程池是有狀態(tài)的,不同狀態(tài)下線程池的行為是不一樣的,5種狀態(tài)已經(jīng)在上面說過了。


  另外線程池肯定是需要線程去執(zhí)行具體的任務(wù)的,所以在線程池中就封裝了一個內(nèi)部類Worker作為工作線程,每個Worker中都維持著一個Thread。


  線程池的重點之一就是控制線程資源合理高效的使用,所以必須控制工作線程的個數(shù),所以需要保存當(dāng)前線程池中工作線程的個數(shù)。


  看到這里,你是否覺得需要用兩個變量來保存線程池的狀態(tài)和線程池中工作線程的個數(shù)呢?但是在ThreadPoolExecutor中只用了一個AtomicInteger型的變量就保存了這兩個屬性的值,那就是ctl。

image.png

  ctl的高3位用來表示線程池的狀態(tài)(runState),低29位用來表示工作線程的個數(shù)(workerCnt),為什么要用3位來表示線程池的狀態(tài)呢,原因是線程池一共有5種狀態(tài),而2位只能表示出4種情況,所以至少需要3位才能表示得了5種狀態(tài)。


  2、核心線程數(shù)和最大線程數(shù)


  現(xiàn)在有了標(biāo)志工作線程的個數(shù)的變量了,那到底該有多少個線程才合適呢?線程多了浪費線程資源,少了又不能發(fā)揮線程池的性能。


  為了解決這個問題,線程池設(shè)計了兩個變量來協(xié)作,分別是:


  核心線程數(shù):corePoolSize用來表示線程池中的核心線程的數(shù)量,也可以稱為可閑置的線程數(shù)量最大線程數(shù):maximumPoolSize用來表示線程池中最多能夠創(chuàng)建的線程數(shù)量


  現(xiàn)在我們有一個疑問,既然已經(jīng)有了標(biāo)識工作線程的個數(shù)的變量了,為什么還要有核心線程數(shù)、最大線程數(shù)呢?


  其實你這樣想就能夠理解了,創(chuàng)建線程是有代價的,不能每次要執(zhí)行一個任務(wù)時就創(chuàng)建一個線程,但是也不能在任務(wù)非常多的時候,只有少量的線程在執(zhí)行,這樣任務(wù)是來不及處理的,而是應(yīng)該創(chuàng)建合適的足夠多的線程來及時的處理任務(wù)。隨著任務(wù)數(shù)量的變化,當(dāng)任務(wù)數(shù)明顯很小時,原本創(chuàng)建的多余的線程就沒有必要再存活著了,因為這時使用少量的線程就能夠處理的過來了,所以說真正工作的線程的數(shù)量,是隨著任務(wù)的變化而變化的。


  那核心線程數(shù)和最大線程數(shù)與工作線程個數(shù)的關(guān)系是什么呢?

image.png

  工作線程的個數(shù)可能從0到最大線程數(shù)之間變化,當(dāng)執(zhí)行一段時間之后可能維持在corePoolSize,但也不是絕對的,取決于核心線程是否允許被超時回收。


  3、創(chuàng)建線程的工廠


  既然是線程池,那自然少不了線程,線程該如何來創(chuàng)建呢?這個任務(wù)就交給了線程工廠ThreadFactory來完成。


  4、緩存任務(wù)的阻塞隊列


  上面我們說了核心線程數(shù)和最大線程數(shù),并且也介紹了工作線程的個數(shù)是在0和最大線程數(shù)之間變化的。但是不可能一下子就創(chuàng)建了所有線程,把線程池裝滿,而是有一個過程,這個過程是這樣的:


  當(dāng)線程池接收到一個任務(wù)時,如果工作線程數(shù)沒有達(dá)到corePoolSize,那么就會新建一個線程,并綁定該任務(wù),直到工作線程的數(shù)量達(dá)到corePoolSize前都不會重用之前的線程。


  當(dāng)工作線程數(shù)達(dá)到corePoolSize了,這時又接收到新任務(wù)時,會將任務(wù)存放在一個阻塞隊列中等待核心線程去執(zhí)行。為什么不直接創(chuàng)建更多的線程來執(zhí)行新任務(wù)呢,原因是核心線程中很可能已經(jīng)有線程執(zhí)行完自己的任務(wù)了,或者有其他線程馬上就能處理完當(dāng)前的任務(wù),并且接下來就能投入到新的任務(wù)中去,所以阻塞隊列是一種緩沖的機(jī)制,給核心線程一個機(jī)會讓他們充分發(fā)揮自己的能力。另外一個值得考慮的原因是,創(chuàng)建線程畢竟是比較昂貴的,不可能一有任務(wù)要執(zhí)行就去創(chuàng)建一個新的線程。


  所以我們需要為線程池配備一個阻塞隊列,用來臨時緩存任務(wù),這些任務(wù)將等待工作線程來執(zhí)行。

image.png

  5、非核心線程存活時間


  上面我們說了當(dāng)工作線程數(shù)達(dá)到corePoolSize時,線程池會將新接收到的任務(wù)存放在阻塞隊列中,而阻塞隊列又兩種情況:一種是有界的隊列,一種是無界的隊列。


  如果是無界隊列,那么當(dāng)核心線程都在忙的時候,所有新提交的任務(wù)都會被存放在該無界隊列中,這時最大線程數(shù)將變得沒有意義,因為阻塞隊列不會存在被裝滿的情況。


  如果是有界隊列,那么當(dāng)阻塞隊列中裝滿了等待執(zhí)行的任務(wù),這時再有新任務(wù)提交時,線程池就需要創(chuàng)建新的“臨時”線程來處理,相當(dāng)于增派人手來處理任務(wù)。


  但是創(chuàng)建的“臨時”線程是有存活時間的,不可能讓他們一直都存活著,當(dāng)阻塞隊列中的任務(wù)被執(zhí)行完畢,并且又沒有那么多新任務(wù)被提交時,“臨時”線程就需要被回收銷毀,在被回收銷毀之前等待的這段時間,就是非核心線程的存活時間,也就是keepAliveTime屬性。


  那么什么是“非核心線程”呢?是不是先創(chuàng)建的線程就是核心線程,后創(chuàng)建的就是非核心線程呢?


  其實核心線程跟創(chuàng)建的先后沒有關(guān)系,而是跟工作線程的個數(shù)有關(guān),如果當(dāng)前工作線程的個數(shù)大于核心線程數(shù),那么所有的線程都可能是“非核心線程”,都有被回收的可能。


  一個線程執(zhí)行完了一個任務(wù)后,會去阻塞隊列里面取新的任務(wù),在取到任務(wù)之前它就是一個閑置的線程。


  取任務(wù)的方法有兩種,一種是通過take()方法一直阻塞直到取出任務(wù),另一種是通過poll(keepAliveTime,timeUnit)方法在一定時間內(nèi)取出任務(wù)或者超時,如果超時這個線程就會被回收,請注意核心線程一般不會被回收。


  那么怎么保證核心線程不會被回收呢?還是跟工作線程的個數(shù)有關(guān),每一個線程在取任務(wù)的時候,線程池會比較當(dāng)前的工作線程個數(shù)與核心線程數(shù):


  如果工作線程數(shù)小于當(dāng)前的核心線程數(shù),則使用第一種方法取任務(wù),也就是沒有超時回收,這時所有的工作線程都是“核心線程”,他們不會被回收;如果大于核心線程數(shù),則使用第二種方法取任務(wù),一旦超時就回收,所以并沒有絕對的核心線程,只要這個線程沒有在存活時間內(nèi)取到任務(wù)去執(zhí)行就會被回收。


  所以每個線程想要保住自己“核心線程”的身份,必須充分努力,盡可能快的獲取到任務(wù)去執(zhí)行,這樣才能逃避被回收的命運。


  核心線程一般不會被回收,但是也不是絕對的,如果我們設(shè)置了允許核心線程超時被回收的話,那么就沒有核心線程這種說法了,所有的線程都會通過poll(keepAliveTime,timeUnit)來獲取任務(wù),一旦超時獲取不到任務(wù),就會被回收,一般很少會這樣來使用,除非該線程池需要處理的任務(wù)非常少,并且頻率也不高,不需要將核心線程一直維持著。


  6、拒絕策略


  雖然我們有了阻塞隊列來對任務(wù)進(jìn)行緩存,這從一定程度上為線程池的執(zhí)行提供了緩沖期,但是如果是有界的阻塞隊列,那就存在隊列滿的情況,也存在工作線程的數(shù)據(jù)已經(jīng)達(dá)到最大線程數(shù)的時候。如果這時候再有新的任務(wù)提交時,顯然線程池已經(jīng)心有余而力不足了,因為既沒有空余的隊列空間來存放該任務(wù),也無法創(chuàng)建新的線程來執(zhí)行該任務(wù)了,所以這時我們就需要有一種拒絕策略,即handler。


  拒絕策略是一個RejectedExecutionHandler類型的變量,用戶可以自行指定拒絕的策略,如果不指定的話,線程池將使用默認(rèn)的拒絕策略:拋出異常。


  在線程池中還為我們提供了很多其他可以選擇的拒絕策略:


  直接丟棄該任務(wù)使用調(diào)用者線程執(zhí)行該任務(wù)丟棄任務(wù)隊列中的最老的一個任務(wù),然后提交該任務(wù)


  工作流程


  了解了線程池中所有的重要屬性之后,現(xiàn)在我們需要來了解下線程池的工作流程了。

image.png

  上圖是一張線程池工作的精簡圖,實際的過程比這個要復(fù)雜的多,不過這些應(yīng)該能夠完全覆蓋到線程池的整個工作流程了。


  整個過程可以拆分成以下幾個部分:


  1、提交任務(wù)


  當(dāng)向線程池提交一個新的任務(wù)時,線程池有三種處理情況,分別是:創(chuàng)建一個工作線程來執(zhí)行該任務(wù)、將任務(wù)加入阻塞隊列、拒絕該任務(wù)。


  提交任務(wù)的過程也可以拆分成以下幾個部分:


  當(dāng)工作線程數(shù)小于核心線程數(shù)時,直接創(chuàng)建新的核心工作線程當(dāng)工作線程數(shù)不小于核心線程數(shù)時,就需要嘗試將任務(wù)添加到阻塞隊列中去如果能夠加入成功,說明隊列還沒有滿,那么需要做以下的二次驗證來保證添加進(jìn)去的任務(wù)能夠成功被執(zhí)行驗證當(dāng)前線程池的運行狀態(tài),如果是非RUNNING狀態(tài),則需要將任務(wù)從阻塞隊列中移除,然后拒絕該任務(wù)驗證當(dāng)前線程池中的工作線程的個數(shù),如果為0,則需要主動添加一個空工作線程來執(zhí)行剛剛添加到阻塞隊列中的任務(wù)如果加入失敗,則說明隊列已經(jīng)滿了,那么這時就需要創(chuàng)建新的“臨時”工作線程來執(zhí)行任務(wù)如果創(chuàng)建成功,則直接執(zhí)行該任務(wù)如果創(chuàng)建失敗,則說明工作線程數(shù)已經(jīng)等于最大線程數(shù)了,則只能拒絕該任務(wù)了


  整個過程可以用下面這張圖來表示:

image.png

  2、創(chuàng)建工作線程


  創(chuàng)建工作線程需要做一系列的判斷,需要確保當(dāng)前線程池可以創(chuàng)建新的線程之后,才能創(chuàng)建。


  首先,當(dāng)線程池的狀態(tài)是SHUTDOWN或者STOP時,則不能創(chuàng)建新的線程。


  另外,當(dāng)線程工廠創(chuàng)建線程失敗時,也不能創(chuàng)建新的線程。


  還有就是當(dāng)前工作線程的數(shù)量與核心線程數(shù)、最大線程數(shù)進(jìn)行比較,如果前者大于后者的話,也不允許創(chuàng)建。


  除此之外,會嘗試通過CAS來自增工作線程的個數(shù),如果自增成功了,則會創(chuàng)建新的工作線程,即Worker對象。


  然后加鎖進(jìn)行二次驗證是否能夠創(chuàng)建工作線程,最后如果創(chuàng)建成功,則會啟動該工作線程。


  3、啟動工作線程


  當(dāng)工作線程創(chuàng)建成功后,也就是Worker對象已經(jīng)創(chuàng)建好了,這時就需要啟動該工作線程,讓線程開始干活了,Worker對象中關(guān)聯(lián)著一個Thread,所以要啟動工作線程的話,只要通過worker.thread.start()來啟動該線程即可。


  啟動完了之后,就會執(zhí)行Worker對象的run方法,因為Worker實現(xiàn)了Runnable接口,所以本質(zhì)上Worker也是一個線程。


  通過線程start開啟之后就會調(diào)用到Runnable的run方法,在worker對象的run方法中,調(diào)用了runWorker(this)方法,也就是把當(dāng)前對象傳遞給了runWorker方法,讓他來執(zhí)行。


  4、獲取任務(wù)并執(zhí)行


  在runWorker方法被調(diào)用之后,就是執(zhí)行具體的任務(wù)了,首先需要拿到一個可以執(zhí)行的任務(wù),而Worker對象中默認(rèn)綁定了一個任務(wù),如果該任務(wù)不為空的話,那么就是直接執(zhí)行。


  執(zhí)行完了之后,就會去阻塞隊列中獲取任務(wù)來執(zhí)行,而獲取任務(wù)的過程,需要考慮當(dāng)前工作線程的個數(shù)。


  如果工作線程數(shù)大于核心線程數(shù),那么就需要通過poll來獲取,因為這時需要對閑置的線程進(jìn)行回收;如果工作線程數(shù)小于等于核心線程數(shù),那么就可以通過take來獲取了,因此這時所有的線程都是核心線程,不需要進(jìn)行回收,前提是沒有設(shè)置allowCoreThreadTimeOut


  以上就是動力節(jié)點java培訓(xùn)機(jī)構(gòu)小編分享的“Java視頻教程:Java線程池的工作原理”的內(nèi)容,希望能夠幫助到各位小伙伴們,更多java最新資訊請關(guān)注動力節(jié)點java培訓(xùn)機(jī)構(gòu)官網(wǎng),每天會有精彩內(nèi)容分享與你。


提交申請后,顧問老師會電話與您溝通安排學(xué)習(xí)

免費課程推薦 >>
技術(shù)文檔推薦 >>
主站蜘蛛池模板: 在线不卡视频 | 农村野jizz外jizz农民 | 1000部羞羞禁止免费观看视频 | 真实的国产乱xxxx | 亚洲一区二区影院 | 亚洲精品国产一区二区 | 国产首页精品 | 欧美伊人久久大香线蕉综合69 | 香蕉一区| 国产亚洲欧美另类一区二区三区 | 米奇777第四久久久99 | 狠狠色噜噜噜噜狠狠狠狠狠狠奇米 | 精品欧美一区二区三区 | 成年人色视频 | 国产美女a做受大片在线观看 | 四虎影视884a精品国产古代 | 久热在线 | 久操美女 | 人人爱人人性 | 亚洲色图二区 | 久久综合九九亚洲一区 | 天海翼一区二区在线观看 | 久久精彩免费视频 | 久久国产精品免费视频 | 四虎影音先锋 | 午夜在线观看免费影院 | 爱爱免费网站 | 伊人网综合在线视频 | 69性影院在线观看国产精品87 | 日本在线不卡免费视频一区 | 国产自精品在线 | 泰国一级毛片aaa下面毛多 | 四虎影院永久网址 | 亚洲视频在线观看视频 | 色噜噜五月综合激情久久爱 | 色综合欧美亚洲另类久久 | 国产精品入口麻豆 | 亚洲狼人综合干 | 日韩国产成人精品视频 | 国产成人一区在线播放 | 欧美视频一区二区三区 |