java 线程执行完就会回收吗_Java线程池技术Executors的这个坑你踩过吗?
線程池技術(shù)是Java的一大特性,如果我們想要編寫高并發(fā)、高吞吐的程序,線程池的技術(shù)使用是必須的。對(duì)于很多程序員來(lái)說(shuō),多線程和線程池技術(shù)都了然于胸,基本原理和使用都數(shù)量掌握,分分鐘可以寫出一個(gè)生產(chǎn)消費(fèi)者模式的多線程,也能寫出線程集合和線程相互等待的業(yè)務(wù)功能。但是,在使用多線程和線程池技術(shù)的過程中,我們會(huì)碰到各種各樣詭異的問題,這種問題會(huì)莫名其妙的出現(xiàn),我們很難定位。本文就示例說(shuō)明關(guān)于阻塞線程的一大坑,各位Java程序員你是否踩過?我們應(yīng)該如何避免呢?
四種線程池策略
Java的JUC包中有四種內(nèi)置的創(chuàng)建線程池的策略,借助這些方策略,我們可以很方便的創(chuàng)建一個(gè)線程池對(duì)象在業(yè)務(wù)中使用。這四個(gè)策略分別為:
- 1、NewSingleThreadExecutor,單線程的線程池
單線程的線程池中只有一個(gè)線程,也就是所有任務(wù)都是通過這一個(gè)線程來(lái)串行執(zhí)行,保證了執(zhí)行任務(wù)按照提交順序來(lái)執(zhí)行。如果業(yè)務(wù)只是涉及到異步執(zhí)行,可選擇該線程池技術(shù)。
- 2、NewFixedThreadPool,固定數(shù)量線程的線程池
固定大小的線程池創(chuàng)建的時(shí)候限制了線程池中線程的數(shù)量。當(dāng)有新的任務(wù)創(chuàng)建的時(shí)候,就創(chuàng)建一個(gè)新的線程來(lái)執(zhí)行任務(wù)。當(dāng)線程池中的線程數(shù)量達(dá)到設(shè)定的值的時(shí)候,任務(wù)開始等待執(zhí)行,直到有可用的線程。
- 3、NewCachedThreadPool,可緩存的線程池
可緩存的線程池不限定線程數(shù)量,當(dāng)有新的任務(wù)加入,如果沒有可用的線程,那就創(chuàng)建新的線程。線程最大數(shù)量取決于操作系統(tǒng)所能創(chuàng)建的最大線程數(shù)量。此線程池技術(shù)也是項(xiàng)目開發(fā)中不被推薦的一種方法,因?yàn)槿绾慰刂撇缓萌蝿?wù)數(shù)量,將會(huì)導(dǎo)致大量的線程被創(chuàng)建,影響系統(tǒng)的性能。
- 4、NewScheduledThreadPool,定時(shí)執(zhí)行的線程池
定時(shí)執(zhí)行的線程池支持創(chuàng)建無(wú)線數(shù)量的線程池,并且線程按照設(shè)定的時(shí)間周期執(zhí)行。
這是四種線程池策略。我們可以通過JUC的相關(guān)API來(lái)創(chuàng)建相應(yīng)的線程池,具體需要?jiǎng)?chuàng)建什么樣的線程池策略,根據(jù)業(yè)務(wù)需求。如果只是讓某個(gè)業(yè)務(wù)異步執(zhí)行,那就使用NewSingleThreadExecutor,如果想定義固定線程數(shù)量那就使用NewFixedThreadPool,如果是啟動(dòng)定時(shí)任務(wù)來(lái)執(zhí)行業(yè)務(wù)邏輯,那就使用NewScheduledThreadPool。
Executors導(dǎo)致的”大坑“
使用這四種線程池技術(shù)看似簡(jiǎn)單,通過相應(yīng)的API就能快速創(chuàng)建。就是因?yàn)镴UC API多于線程池技術(shù)的封裝,導(dǎo)致我們?cè)谑褂镁€程池技術(shù)會(huì)遇到一大坑。
Executors是JUC中一個(gè)核心的創(chuàng)建線程池的工具類,通過Executors我們可以快速的創(chuàng)建不同策略的線程池。Executors中的方法如下所示:
我們可以看到Executors類中提供了很多方法用來(lái)創(chuàng)建線程池。例如:我們要?jiǎng)?chuàng)建一個(gè)固定大小的線程池,示例代碼如下:
那這段代碼有沒有什么問題呢?沒有問題。但是,如果是在正常的業(yè)務(wù)流程中,就會(huì)有很大的問題,我們舉一個(gè)實(shí)際的業(yè)務(wù)場(chǎng)景:
某個(gè)業(yè)務(wù)系統(tǒng)中,需要消費(fèi)Kafka中的消息,然后將消息進(jìn)行處理存庫(kù)。Kafka消費(fèi)消息的速度很快,但是入庫(kù)的速度很慢,因?yàn)槭窍M(fèi)和入庫(kù)是同步執(zhí)行的,導(dǎo)致Kafka的消息有擠壓。所以我們需要將消費(fèi)和入庫(kù)分開異步執(zhí)行,異步執(zhí)行我們想到了使用線程池技術(shù),我們可以開啟固定數(shù)量線程的線程池技策略來(lái)多線程的入庫(kù)。我們模擬一下場(chǎng)景代碼:
通過測(cè)試發(fā)現(xiàn),100次的循環(huán)很快結(jié)束,(即100條kafka消息很快消費(fèi)完成)。所有的待處理任務(wù)全部加入了executorService的待處理隊(duì)列任務(wù)中。如果循環(huán)1000次,executorService的隊(duì)列中有大量的待處理任務(wù),從而導(dǎo)致系統(tǒng)OOM。
通過研究源碼我們可以發(fā)現(xiàn),Executors.newFixedThreadPool(10)內(nèi)部調(diào)用的ThreadPoolExecutor默認(rèn)采用的阻塞隊(duì)列是LinkedBlockingQueue,并且的它的長(zhǎng)度是隊(duì)列長(zhǎng)度是Integer.MAX_VALUE,即2147483647。這就是導(dǎo)致堆積大量的請(qǐng)求,系統(tǒng)發(fā)生OOM的原因。
這就是用Executors創(chuàng)建線程池的一大坑,它隱藏了內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)。如果我們直接使用Executors來(lái)創(chuàng)建線程池,這對(duì)于高并發(fā)系統(tǒng)來(lái)說(shuō)就是一場(chǎng)災(zāi)難。
ThreadPoolExecutor創(chuàng)建線程池
那回到上面的例子,我們?nèi)绾谓鉀Q這個(gè)問題呢?通過ThreadPoolExecutor直接來(lái)創(chuàng)建線程池,示例代碼如下:
在ThreadPoolExecutor的構(gòu)造方法中,我們可以定義阻塞隊(duì)列的大小,這樣就不會(huì)讓線程池隊(duì)列無(wú)限大而導(dǎo)致OOM了。但是,這里面又有一個(gè)坑:當(dāng)阻塞隊(duì)列滿員,后面要再加入任務(wù),會(huì)報(bào)錯(cuò),如下錯(cuò)誤所示:
ArrayBlockingQueue Offer方法的”坑“
通過研究源碼我們發(fā)現(xiàn),ThreadPoolExecutor中阻塞隊(duì)列添加任務(wù)時(shí)采用的是offer方法,如下圖所示:
我們都知道,LinkedBlockingQueue、ArrayBlockingQueue以及其他阻塞隊(duì)列,offer方法在添加元素的時(shí)候,如果隊(duì)列已滿無(wú)法添加元素的時(shí)候,offer方法會(huì)直接返回false,這就會(huì)導(dǎo)致上面的異常。
自定義阻塞隊(duì)列
那如果我們要實(shí)現(xiàn)一個(gè)阻塞線程池隊(duì)列,我們改如何實(shí)現(xiàn)呢?自定義阻塞隊(duì)列,覆寫offer方法,示例代碼如下:
自定義阻塞隊(duì)列:
對(duì)于put方法,若向隊(duì)尾添加元素的時(shí)候發(fā)現(xiàn)隊(duì)列已經(jīng)滿了會(huì)發(fā)生阻塞,這正好滿足我們的需求。改造后的代碼如下:
通過運(yùn)行我們發(fā)現(xiàn)滿足我們的需求。
不會(huì)堆積大量的隊(duì)列任務(wù)。
總結(jié)
Java線程池技術(shù)在業(yè)務(wù)中需要慎重使用,對(duì)于線程池技術(shù)的內(nèi)部實(shí)現(xiàn)機(jī)制我們需要精通掌握,才能在實(shí)際項(xiàng)目中熟練使用。文中涉及到的”坑“大家還需要好好消化理解,在以后的項(xiàng)目開發(fā)中盡量避免。
總結(jié)
以上是生活随笔為你收集整理的java 线程执行完就会回收吗_Java线程池技术Executors的这个坑你踩过吗?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小程序云服务器选什么系统好,小程序云服务
- 下一篇: windows分屏_windows内到底