|
線程復(fù)用:線程池 首先舉個(gè)例子: 假設(shè)這里有一個(gè)系統(tǒng),大概每秒需要處理5萬條數(shù)據(jù),這5萬條數(shù)據(jù)為一個(gè)批次,而這沒秒發(fā)送的5萬條數(shù)據(jù)數(shù)據(jù)需要經(jīng)過兩個(gè)處理過程,第一步是數(shù)據(jù)存入數(shù)據(jù)庫,第二步是對數(shù)據(jù)進(jìn)行其他業(yè)務(wù)的分析,假設(shè)第一步我是用的是普通的JDBC插入數(shù)據(jù),為了不影響程序的繼續(xù)執(zhí)行,我寫了一個(gè)線程,讓這個(gè)子線程不阻塞主線程,繼續(xù)處理第二步驟的數(shù)據(jù),我們知道插入5萬條數(shù)據(jù)大概需要2至3秒的時(shí)間,如果每一批次插入數(shù)據(jù)庫的時(shí)候,就創(chuàng)建一個(gè)線程進(jìn)行處理,可想而知,由于插入數(shù)據(jù)庫的時(shí)間較久,不能很快的處理,這樣的話,一段時(shí)間之后,系統(tǒng)中就會(huì)有很多的這種插入數(shù)據(jù)的線程(PS:只是假設(shè)場景,方案設(shè)計(jì)的可能不合理)。 如果,我們使用上述的方式去創(chuàng)建線程,使用start()方法啟動(dòng)線程,該線程會(huì)在run()方法結(jié)束后,自動(dòng)回收該線程。雖然如此,在上邊的場景中線程中業(yè)務(wù)的處理速度完全達(dá)不到我們的要求,系統(tǒng)中的線程會(huì)逐漸變大,進(jìn)而消耗CPU資源,大量的線程搶占寶貴的內(nèi)存資源,可能還會(huì)出現(xiàn)OOM,即便沒有出現(xiàn),大量的線程回收也會(huì)個(gè)GC帶來很大的壓力。 可想而知,雖然多線程技術(shù)可以充分發(fā)揮多核處理器的計(jì)算能力,提高生產(chǎn)系統(tǒng)的吞吐量和性能。但是,若不加控制和管理的隨意使用線程,對系統(tǒng)的性能反而會(huì)產(chǎn)生不利的影響。 還拿上邊的例子說,如果我們使用線程池的方式的話,可以實(shí)現(xiàn)最多創(chuàng)建愛你線程的數(shù)量,這樣的話就算再多的數(shù)據(jù)需要入庫,只需要排隊(duì)等待線程池的線程即可,就不會(huì)出現(xiàn)線程池過多而消耗系統(tǒng)資源的情況,當(dāng)然這只是意見簡單的場景。 說到這里,有人要說了線程不是攜帶資源的最小單位,操作系統(tǒng)的書籍中還給我們說了線程之間的切換消耗很小嗎?雖然如此,線程是一種輕量級的工具(或者稱之為:輕量級進(jìn)程),但其創(chuàng)建和關(guān)閉依然需要花費(fèi)時(shí)間,如果為了一個(gè)很簡單的任務(wù)就去創(chuàng)建一個(gè)線程,很有可能出現(xiàn)創(chuàng)建和銷毀線程所占用的時(shí)間大于該線程真實(shí)工作所消耗的時(shí)間,反而得不償失。 那么什么是線程池? 為了避免系統(tǒng)頻繁的創(chuàng)建和銷毀線程,我們可以將創(chuàng)建的線程進(jìn)行復(fù)用。數(shù)據(jù)庫中的數(shù)據(jù)庫連接池也是此意。 在線程池中總有那么幾個(gè)活躍的線程,也有一定的最大值限制,一個(gè)業(yè)務(wù)使用完線程之后,不是立即銷毀而是將其放入到線程池中,從而實(shí)現(xiàn)線程的復(fù)用。簡而言之:創(chuàng)建線程變成了從線程池獲取空閑的線程,關(guān)閉線程變成了向池子中歸還線程。 再多的概念,不過多解釋,因?yàn)楹芑A(chǔ),也不是本文的重點(diǎn)。 JDK對線程池的支持 JDK提供的Eexecutor框架 JDK提供了Executor框架,可以讓我們有效的管理和控制我們的線程,其實(shí)質(zhì)也就是一個(gè)線程池。Executor下的接口和類繼承關(guān)系如下: 其中,ExecutorService接口定義如下: 如果使用Executor框架的話,Executors類是常用的,其方法如下: 其中常用幾類如下: 1、newFixedThreadPool:該方法返回一個(gè)固定線程數(shù)量的線程池; 2、newSingleThreadExecutor:該方法返回一個(gè)只有一個(gè)現(xiàn)成的線程池; 3、newCachedThreadPool:返回一個(gè)可以根據(jù)實(shí)際情況調(diào)整線程數(shù)量的線程池; 4、newSingleThreadScheduledExecutor:該方法和newSingleThreadExecutor的區(qū)別是給定了時(shí)間執(zhí)行某任務(wù)的功能,可以進(jìn)行定時(shí)執(zhí)行等; 5、newScheduledThreadPool:在4的基礎(chǔ)上可以指定線程數(shù)量。 創(chuàng)建線程池是指調(diào)用的還是ThreadPoolExecutor 在Executors類中,我們拿出來一個(gè)方法簡單分析一下: 可以看出,類似的其他方法一樣,在Executors內(nèi)部創(chuàng)建線程池的時(shí)候,實(shí)際創(chuàng)建的都是一個(gè)ThreadPoolExecutor對象,只是對ThreadPoolExecutor構(gòu)造方法,進(jìn)行了默認(rèn)值的設(shè)定。ThreadPoolExecutor的構(gòu)造方法如下: 參數(shù)含義如下: Eexecutor框架實(shí)例 1、實(shí)例一:
submit(Runnable task)方法提交一個(gè)線程。 但是使用最新的“阿里巴巴編碼規(guī)范插件”檢測一下會(huì)發(fā)現(xiàn):
阿里巴巴編碼規(guī)范插件地址:https://github.com/alibaba/p3c 2、實(shí)例二: 遵循阿里巴巴編碼規(guī)范的提示,示例如下:
或者這樣:
3、實(shí)例三: 自定義ThreadFactory、自定義線程拒絕策略
更多實(shí)例代碼,可參考: https:///xuliugen/codes/ta5dbsge0kvhy62qu8li157 使用submit的坑 首先看一下實(shí)例:
運(yùn)行結(jié)果:
上述代碼,可以看出運(yùn)行結(jié)果為4個(gè),因該是有5個(gè)的,但是當(dāng)i=0的時(shí)候,100/0是會(huì)報(bào)錯(cuò)的,但是日志信息中沒有任何信息,是為什么那?如果使用了submit(Runnable task) 就會(huì)出現(xiàn)這種情況,任何的錯(cuò)誤信息都出現(xiàn)不了! 這是因?yàn)槭褂胹ubmit(Runnable task) 的時(shí)候,錯(cuò)誤的堆棧信息跑出來的時(shí)候會(huì)被內(nèi)部捕獲到,所以打印不出來具體的信息讓我們查看,解決的方法有如下兩種: 1、使用execute()代替submit();
運(yùn)行結(jié)果:
2、使用Future
運(yùn)行結(jié)果:
|
|
|