關(guān)于線(xiàn)程和線(xiàn)程池的學(xué)習(xí)授霸,我們可以從以下幾個(gè)方面入手:
第一巡验,什么是線(xiàn)程,線(xiàn)程和進(jìn)程的區(qū)別是什么
第二碘耳,線(xiàn)程中的基本概念显设,線(xiàn)程的生命周期
第三,單線(xiàn)程和多線(xiàn)程
第四辛辨,線(xiàn)程池的原理解析
第五捕捂,常見(jiàn)的幾種線(xiàn)程池的特點(diǎn)以及各自的應(yīng)用場(chǎng)景
一瑟枫、什么是線(xiàn)程
線(xiàn)程,程序執(zhí)行流的最小執(zhí)行單位指攒,是行程中的實(shí)際運(yùn)作單位慷妙,經(jīng)常容易和進(jìn)程這個(gè)概念混淆。那么允悦,線(xiàn)程和進(jìn)程究竟有什么區(qū)別呢膝擂?首先,進(jìn)程是一個(gè)動(dòng)態(tài)的過(guò)程隙弛,是一個(gè)活動(dòng)的實(shí)體架馋。簡(jiǎn)單來(lái)說(shuō),一個(gè)應(yīng)用程序的運(yùn)行就可以被看做是一個(gè)進(jìn)程全闷,而線(xiàn)程叉寂,是運(yùn)行中的實(shí)際的任務(wù)執(zhí)行者∽苤椋可以說(shuō)屏鳍,進(jìn)程中包含了多個(gè)可以同時(shí)運(yùn)行的線(xiàn)程。
二姚淆、線(xiàn)程的生命周期
線(xiàn)程的生命周期可以利用以下的圖解來(lái)更好的理解:
第一步孕蝉,是用new Thread()的方法新建一個(gè)線(xiàn)程,在線(xiàn)程創(chuàng)建完成之后腌逢,線(xiàn)程就進(jìn)入了就緒(Runnable)狀態(tài),此時(shí)創(chuàng)建出來(lái)的線(xiàn)程進(jìn)入搶占CPU資源的狀態(tài)超埋,當(dāng)線(xiàn)程搶到了CPU的執(zhí)行權(quán)之后搏讶,線(xiàn)程就進(jìn)入了運(yùn)行狀態(tài)(Running),當(dāng)該線(xiàn)程的任務(wù)執(zhí)行完成之后或者是非常態(tài)的調(diào)用的stop()方法之后霍殴,線(xiàn)程就進(jìn)入了死亡狀態(tài)媒惕。而我們?cè)趫D解中可以看出,線(xiàn)程還具有一個(gè)則色的過(guò)程来庭,這是怎么回事呢妒蔚?當(dāng)面對(duì)以下幾種情況的時(shí)候,容易造成線(xiàn)程阻塞月弛,第一種肴盏,當(dāng)線(xiàn)程主動(dòng)調(diào)用了sleep()方法時(shí),線(xiàn)程會(huì)進(jìn)入則阻塞狀態(tài)帽衙,除此之外菜皂,當(dāng)線(xiàn)程中主動(dòng)調(diào)用了阻塞時(shí)的IO方法時(shí),這個(gè)方法有一個(gè)返回參數(shù)厉萝,當(dāng)參數(shù)返回之前恍飘,線(xiàn)程也會(huì)進(jìn)入阻塞狀態(tài)榨崩,還有一種情況,當(dāng)線(xiàn)程進(jìn)入正在等待某個(gè)通知時(shí)章母,會(huì)進(jìn)入阻塞狀態(tài)母蛛。那么,為什么會(huì)有阻塞狀態(tài)出現(xiàn)呢乳怎?我們都知道,CPU的資源是十分寶貴的彩郊,所以,當(dāng)線(xiàn)程正在進(jìn)行某種不確定時(shí)長(zhǎng)的任務(wù)時(shí)舞肆,Java就會(huì)收回CPU的執(zhí)行權(quán)焦辅,從而合理應(yīng)用CPU的資源。我們根據(jù)圖可以看出椿胯,線(xiàn)程在阻塞過(guò)程結(jié)束之后筷登,會(huì)重新進(jìn)入就緒狀態(tài),重新?lián)寠ZCPU資源哩盲。這時(shí)候前方,我們可能會(huì)產(chǎn)生一個(gè)疑問(wèn),如何跳出阻塞過(guò)程呢?又以上幾種可能造成線(xiàn)程阻塞的情況來(lái)看廉油,都是存在一個(gè)時(shí)間限制的惠险,當(dāng)sleep()方法的睡眠時(shí)長(zhǎng)過(guò)去后,線(xiàn)程就自動(dòng)跳出了阻塞狀態(tài)抒线,第二種則是在返回了一個(gè)參數(shù)之后班巩,在獲取到了等待的通知時(shí),就自動(dòng)跳出了線(xiàn)程的阻塞過(guò)程
三嘶炭、什么是單線(xiàn)程和多線(xiàn)程抱慌?
單線(xiàn)程,顧名思義即是只有一條線(xiàn)程在執(zhí)行任務(wù)眨猎,這種情況在我們?nèi)粘5墓ぷ鲗W(xué)習(xí)中很少遇到抑进,所以我們只是簡(jiǎn)單做一下了解
多線(xiàn)程,創(chuàng)建多條線(xiàn)程同時(shí)執(zhí)行任務(wù)睡陪,這種方式在我們的日常生活中比較常見(jiàn)寺渗。但是,在多線(xiàn)程的使用過(guò)程中兰迫,還有許多需要我們了解的概念信殊。比如,在理解上并行和并發(fā)的區(qū)別逮矛,以及在實(shí)際應(yīng)用的過(guò)程中多線(xiàn)程的安全問(wèn)題鸡号,對(duì)此,我們需要進(jìn)行詳細(xì)的了解须鼎。
并行和并發(fā):在我們看來(lái)鲸伴,都是可以同時(shí)執(zhí)行多種任務(wù)府蔗,那么,到底他們二者有什么區(qū)別呢汞窗?
并發(fā)姓赤,從宏觀方面來(lái)說(shuō),并發(fā)就是同時(shí)進(jìn)行多種時(shí)間仲吏,實(shí)際上不铆,這幾種時(shí)間,并不是同時(shí)進(jìn)行的裹唆,而是交替進(jìn)行的誓斥,而由于CPU的運(yùn)算速度非常的快,會(huì)造成我們的一種錯(cuò)覺(jué)许帐,就是在同一時(shí)間內(nèi)進(jìn)行了多種事情
而并發(fā)劳坑,則是真正意義上的同時(shí)進(jìn)行多種事情。這種只可以在多核CPU的基礎(chǔ)下完成成畦。
還有就是多線(xiàn)程的安全問(wèn)題距芬?為什么會(huì)造成多線(xiàn)程的安全問(wèn)題呢?我們可以想象一下循帐,如果多個(gè)線(xiàn)程同時(shí)執(zhí)行一個(gè)任務(wù)框仔,name意味著他們共享同一種資源,由于線(xiàn)程CPU的資源不一定可以被誰(shuí)搶占到拄养,這是离斩,第一條線(xiàn)程先搶占到CPU資源,他剛剛進(jìn)行了第一次操作瘪匿,而此時(shí)第二條線(xiàn)程搶占到了CPU的資源捐腿,name,共享資源還來(lái)不及發(fā)生變化柿顶,就同時(shí)有兩條數(shù)據(jù)使用了同一條資源,具體請(qǐng)參考多線(xiàn)程買(mǎi)票問(wèn)題操软。這個(gè)問(wèn)題我們應(yīng)該如何解決那嘁锯?
有造成問(wèn)題的原因我們可以看出,這個(gè)問(wèn)題主要的矛盾在于聂薪,CPU的使用權(quán)搶占和資源的共享發(fā)生了沖突家乘,解決時(shí),我們只需要讓一條線(xiàn)程戰(zhàn)歌了CPU的資源時(shí)藏澳,阻止第二條線(xiàn)程同時(shí)搶占CPU的執(zhí)行權(quán)仁锯,在代碼中,我們只需要在方法中使用同步代碼塊即可翔悠。在這里业崖,同步代碼塊不多進(jìn)行贅述野芒,可以自行了解。
四双炕,線(xiàn)程池
又以上介紹我們可以看出狞悲,在一個(gè)應(yīng)用程序中,我們需要多次使用線(xiàn)程妇斤,也就意味著摇锋,我們需要多次創(chuàng)建并銷(xiāo)毀線(xiàn)程。而創(chuàng)建并銷(xiāo)毀線(xiàn)程的過(guò)程勢(shì)必會(huì)消耗內(nèi)存站超。而在Java中荸恕,內(nèi)存資源是及其寶貴的,所以死相,我們就提出了線(xiàn)程池的概念融求。
線(xiàn)程池:Java中開(kāi)辟出了一種管理線(xiàn)程的概念,這個(gè)概念叫做線(xiàn)程池媳纬,從概念以及應(yīng)用場(chǎng)景中双肤,我們可以看出,線(xiàn)程池的好處钮惠,就是可以方便的管理線(xiàn)程茅糜,也可以減少內(nèi)存的消耗。
那么素挽,我們應(yīng)該如何創(chuàng)建一個(gè)線(xiàn)程池那?Java中已經(jīng)提供了創(chuàng)建線(xiàn)程池的一個(gè)類(lèi):Executor
而我們創(chuàng)建時(shí)蔑赘,一般使用它的子類(lèi):ThreadPoolExecutor.
public?ThreadPoolExecutor(int?corePoolSize,
int?maximumPoolSize,
long?keepAliveTime,
TimeUnit?unit,
BlockingQueueworkQueue,
ThreadFactory?threadFactory,
RejectedExecutionHandler?handler)
這是其中最重要的一個(gè)構(gòu)造方法,這個(gè)方法決定了創(chuàng)建出來(lái)的線(xiàn)程池的各種屬性预明,下面依靠一張圖來(lái)更好的理解線(xiàn)程池和這幾個(gè)參數(shù):
又圖中缩赛,我們可以看出,線(xiàn)程池中的corePoolSize就是線(xiàn)程池中的核心線(xiàn)程數(shù)量撰糠,這幾個(gè)核心線(xiàn)程酥馍,只是在沒(méi)有用的時(shí)候,也不會(huì)被回收阅酪,maximumPoolSize就是線(xiàn)程池中可以容納的最大線(xiàn)程的數(shù)量旨袒,而keepAliveTime,就是線(xiàn)程池中除了核心線(xiàn)程之外的其他的最長(zhǎng)可以保留的時(shí)間术辐,因?yàn)樵诰€(xiàn)程池中砚尽,除了核心線(xiàn)程即使在無(wú)任務(wù)的情況下也不能被清除,其余的都是有存活時(shí)間的辉词,意思就是非核心線(xiàn)程可以保留的最長(zhǎng)的空閑時(shí)間必孤,而util,就是計(jì)算這個(gè)時(shí)間的一個(gè)單位瑞躺,workQueue敷搪,就是等待隊(duì)列兴想,任務(wù)可以?xún)?chǔ)存在任務(wù)隊(duì)列中等待被執(zhí)行,執(zhí)行的是FIFIO原則(先進(jìn)先出)购啄。threadFactory襟企,就是創(chuàng)建線(xiàn)程的線(xiàn)程工廠,最后一個(gè)handler,是一種拒絕策略狮含,我們可以在任務(wù)滿(mǎn)了知乎顽悼,拒絕執(zhí)行某些任務(wù)。
線(xiàn)程池的執(zhí)行流程又是怎樣的呢几迄?
有圖我們可以看出蔚龙,任務(wù)進(jìn)來(lái)時(shí),首先執(zhí)行判斷映胁,判斷核心線(xiàn)程是否處于空閑狀態(tài)木羹,如果不是,核心線(xiàn)程就先就執(zhí)行任務(wù)解孙,如果核心線(xiàn)程已滿(mǎn)坑填,則判斷任務(wù)隊(duì)列是否有地方存放該任務(wù),若果有弛姜,就將任務(wù)保存在任務(wù)隊(duì)列中脐瑰,等待執(zhí)行,如果滿(mǎn)了廷臼,在判斷最大可容納的線(xiàn)程數(shù)苍在,如果沒(méi)有超出這個(gè)數(shù)量,就開(kāi)創(chuàng)非核心線(xiàn)程執(zhí)行任務(wù)荠商,如果超出了寂恬,就調(diào)用handler實(shí)現(xiàn)拒絕策略。
handler的拒絕策略:
有四種:第一種AbortPolicy:不執(zhí)行新任務(wù)莱没,直接拋出異常初肉,提示線(xiàn)程池已滿(mǎn)
第二種DisCardPolicy:不執(zhí)行新任務(wù),也不拋出異常
第三種DisCardOldSetPolicy:將消息隊(duì)列中的第一個(gè)任務(wù)替換為當(dāng)前新進(jìn)來(lái)的任務(wù)執(zhí)行
第四種CallerRunsPolicy:直接調(diào)用execute來(lái)執(zhí)行當(dāng)前任務(wù)
五饰躲,四種常見(jiàn)的線(xiàn)程池:
CachedThreadPool:可緩存的線(xiàn)程池朴译,該線(xiàn)程池中沒(méi)有核心線(xiàn)程,非核心線(xiàn)程的數(shù)量為Integer.max_value属铁,就是無(wú)限大,當(dāng)有需要時(shí)創(chuàng)建線(xiàn)程來(lái)執(zhí)行任務(wù)躬翁,沒(méi)有需要時(shí)回收線(xiàn)程焦蘑,適用于耗時(shí)少,任務(wù)量大的情況盒发。
SecudleThreadPool:周期性執(zhí)行任務(wù)的線(xiàn)程池例嘱,按照某種特定的計(jì)劃執(zhí)行線(xiàn)程中的任務(wù)狡逢,有核心線(xiàn)程,但也有非核心線(xiàn)程拼卵,非核心線(xiàn)程的大小也為無(wú)限大奢浑。適用于執(zhí)行周期性的任務(wù)。
SingleThreadPool:只有一條線(xiàn)程來(lái)執(zhí)行任務(wù)腋腮,適用于有順序的任務(wù)的應(yīng)用場(chǎng)景雀彼。
FixedThreadPool:定長(zhǎng)的線(xiàn)程池,有核心線(xiàn)程即寡,核心線(xiàn)程的即為最大的線(xiàn)程數(shù)量徊哑,沒(méi)有非核心線(xiàn)程
六,線(xiàn)程池
1聪富、配置
@Configuration@EnableAsyncpublicclassTaskPoolConfig{@Bean("taskExecutor")publicExecutortaskExecutro(){? ? ? ? ThreadPoolTaskExecutor taskExecutor =newThreadPoolTaskExecutor();? ? ? ? taskExecutor.setCorePoolSize(10);? ? ? ? taskExecutor.setMaxPoolSize(50);? ? ? ? taskExecutor.setQueueCapacity(200);? ? ? ? taskExecutor.setKeepAliveSeconds(60);? ? ? ? taskExecutor.setThreadNamePrefix("taskExecutor--");? ? ? ? taskExecutor.setWaitForTasksToCompleteOnShutdown(true);? ? ? ? taskExecutor.setAwaitTerminationSeconds(60);returntaskExecutor;? ? }}
2莺丑、使用
@ComponentpublicclassAsyncTask{@Async("taskExecutor")publicvoidtesTask(inti){? ? ? ? System.out.println(Thread.currentThread().getName()+"-----"+i);? ? }@Async("taskExecutor")publicvoidstringTask(String str){? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+str);? ? }}