国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

面試必問的Java 線程池原理及最佳實踐

這篇具有很好參考價值的文章主要介紹了面試必問的Java 線程池原理及最佳實踐。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

1. 概述

1.1 線程池是什么

線程池(Thread Pool)是一種基于池化思想管理線程的工具,經(jīng)常出現(xiàn)在多線程服務器中,如MySQL。

創(chuàng)建線程本身開銷大,反復創(chuàng)建并銷毀,過多的占用內(nèi)存。所以有大量線程創(chuàng)建考慮使用線程池。線程池不用反復創(chuàng)建線程達到線程的復用,更具配置合理利用cpu和內(nèi)存減少了開銷,性能會得到提高,還能統(tǒng)一管理任務

比如服務器收到大量請求,每個請求都分配線程去處理,對服務器性能考驗就比較大,如果創(chuàng)建5個以上線程考慮使用線程池。

線程過多會帶來額外的開銷,其中包括創(chuàng)建銷毀線程的開銷、調(diào)度線程的開銷等等,同時也降低了計算機的整體性能。線程池維護多個線程,等待監(jiān)督管理者分配可并發(fā)執(zhí)行的任務。這種做法,一方面避免了處理任務時創(chuàng)建銷毀線程開銷的代價,另一方面避免了線程數(shù)量膨脹導致的過分調(diào)度問題,保證了對內(nèi)核的充分利用。

而本文描述線程池是JDK中提供的ThreadPoolExecutor類。

當然,使用線程池可以帶來一系列好處:

  • 降低資源消耗:通過池化技術重復利用已創(chuàng)建的線程,降低線程創(chuàng)建和銷毀造成的損耗。

  • 提高響應速度:任務到達時,無需等待線程創(chuàng)建即可立即執(zhí)行。

  • 提高線程的可管理性:線程是稀缺資源,如果無限制創(chuàng)建,不僅會消耗系統(tǒng)資源,還會因為線程的不合理分布導致資源調(diào)度失衡,降低系統(tǒng)的穩(wěn)定性。使用線程池可以進行統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控。

  • 提供更多更強大的功能:線程池具備可拓展性,允許開發(fā)人員向其中增加更多的功能。比如延時定時線程池ScheduledThreadPoolExecutor,就允許任務延期執(zhí)行或定期執(zhí)行。

1.2 線程池解決的問題是什么

線程池解決的核心問題就是資源管理問題。在并發(fā)環(huán)境下,系統(tǒng)不能夠確定在任意時刻中,有多少任務需要執(zhí)行,有多少資源需要投入。這種不確定性將帶來以下若干問題:

  1. 頻繁申請/銷毀資源和調(diào)度資源,將帶來額外的消耗,可能會非常巨大。

  2. 對資源無限申請缺少抑制手段,易引發(fā)系統(tǒng)資源耗盡的風險。

  3. 系統(tǒng)無法合理管理內(nèi)部的資源分布,會降低系統(tǒng)的穩(wěn)定性。

為解決資源分配這個問題,線程池采用了“池化”(Pooling)思想。池化,顧名思義,是為了最大化收益并最小化風險,而將資源統(tǒng)一在一起管理的一種思想。

Pooling is the grouping together of resources (assets, equipment, personnel, effort, etc.) for the purposes of maximizing advantage or minimizing risk to the users. The term is used in finance, computing and equipment management.——wikipedia

“池化”思想不僅僅能應用在計算機領域,在金融、設備、人員管理、工作管理等領域也有相關的應用。

在計算機領域中的表現(xiàn)為:統(tǒng)一管理IT資源,包括服務器、存儲、和網(wǎng)絡資源等等。通過共享資源,使用戶在低投入中獲益。除去線程池,還有其他比較典型的幾種使用策略包括:

  1. 內(nèi)存池(Memory Pooling):預先申請內(nèi)存,提升申請內(nèi)存速度,減少內(nèi)存碎片。

  2. 連接池(Connection Pooling):預先申請數(shù)據(jù)庫連接,提升申請連接的速度,降低系統(tǒng)的開銷。

  3. 實例池(Object Pooling):循環(huán)使用對象,減少資源在初始化和釋放時的昂貴損耗。

在了解完“是什么”和“為什么”之后,下面我們來一起深入一下線程池的內(nèi)部實現(xiàn)原理。

2. 基本使用介紹及其相關參數(shù)和注重點

我們來先看下面兩張類圖

ThreadPoolExecutor

面試必問的Java 線程池原理及最佳實踐

image-20210627142834199

ScheduledThreadPoolExecutor

面試必問的Java 線程池原理及最佳實踐

image-20210627142859123

ThreadPoolExecutorScheduledThreadPoolExecutor算是我們最常用的線程池類了,從上面我們可以看到這倆個最終都實現(xiàn)了ExecutorExecutorService這兩個接口,實際上主要的接口定義都是在ExecutorService

面試必問的Java 線程池原理及最佳實踐

image-20210627143249342

面試必問的Java 線程池原理及最佳實踐

image-20210627143331102

下面我們來著重講解一下ThreadPoolExecutor的使用及其相關介紹

首先我們來看下它的構(gòu)造方法:

public?ThreadPoolExecutor(
???int?corePoolSize,
???int?maximumPoolSize,
???long?keepAliveTime,
???TimeUnit?unit,
???BlockingQueue<Runnable>?workQueue,
???ThreadFactory?threadFactory,
???RejectedExecutionHandler?handler)

參數(shù)說明:

  • corePoolSize:線程池核心線程數(shù)量

線程池在完成初始化之后,默認情況下,線程池中不會有任何線程,線程池會等有任務來的時候再去創(chuàng)建線程。核心線程創(chuàng)建出來后即使超出了線程保持的存活時間配置也不會銷毀,核心線程只要創(chuàng)建就永駐了,就等著新任務進來進行處理。

  • maximumPoolSize:線程池最大線程數(shù)量

核心線程忙不過來且任務存儲隊列滿了的情況下,還有新任務進來的話就會繼續(xù)開辟線程,但是也不是任意的開辟線程數(shù)量,線程數(shù)(包含核心線程)達到maximumPoolSize后就不會產(chǎn)生新線程了,就會執(zhí)行拒絕策略。

  • keepAliverTime:當活躍線程數(shù)大于核心線程數(shù)時,空閑的多余線程最大存活時間

如果線程池當前的線程數(shù)多于corePoolSize,那么如果多余的線程空閑時間超過keepAliveTime,那么這些多余的線程(超出核心線程數(shù)的那些線程)就會被回收。

  • unit:存活時間的單位

比如:TimeUnit.MILLISECONDSTimeUnit.SECONDS

  • workQueue:存放任務的隊列,阻塞隊列類型

核心線程數(shù)滿了后還有任務繼續(xù)提交到線程池的話,就先進入workQueue。

workQueue通常情況下有如下選擇:

LinkedBlockingQueue:無界隊列,意味著無限制,其實是有限制,大小是int的最大值。也可以自定義大小。

ArrayBlockingQueue:有界隊列,可以自定義大小,到了閾值就開啟新線程(不會超過maximumPoolSize)。

SynchronousQueueExecutors.newCachedThreadPool();默認使用的隊列。也不算是個隊列,他不沒有存儲元素的能力。

一般都采取LinkedBlockingQueue,因為他也可以設置大小,可以取代ArrayBlockingQueue有界隊列。

  • threadFactory:當線程池需要新的線程時,會用threadFactory來生成新的線程

默認采用的是DefaultThreadFactory,主要負責創(chuàng)建線程。newThread()方法。創(chuàng)建出來的線程都在同一個線程組且優(yōu)先級也是一樣的。

  • handler:拒絕策略,任務量超出線程池的配置限制或執(zhí)行shutdown還在繼續(xù)提交任務的話,會執(zhí)行handler的邏輯。

默認采用的是AbortPolicy,遇到上面的情況,線程池將直接采取直接拒絕策略,也就是直接拋出異常。RejectedExecutionException

實際上JDK為了我們方便使用線程池,提供了一個Executors工具來為我們快速方便的創(chuàng)建出線程池

Executors創(chuàng)建返回ThreadPoolExecutor對象的方法共有三種:

  • Executors#newCachedThreadPool => 創(chuàng)建可緩存的線程池

  • Executors#newSingleThreadExecutor => 創(chuàng)建單線程的線程池

  • Executors#newFixedThreadPool => 創(chuàng)建固定長度的線程池

Executors#newCachedThreadPool方法

public?static?ExecutorService?newCachedThreadPool()?{
????return?new?ThreadPoolExecutor(0,?Integer.MAX_VALUE,
??????????????????????????????????60L,?TimeUnit.SECONDS,
??????????????????????????????????new?SynchronousQueue<Runnable>());
}

CachedThreadPool是一個根據(jù)需要創(chuàng)建新線程的線程池

  • corePoolSize => 0,核心線程池的數(shù)量為0

  • maximumPoolSize => Integer.MAX_VALUE,可以認為最大線程數(shù)是無限的

  • keepAliveTime => 60L

  • unit => 秒

  • workQueue => SynchronousQueue

當一個任務提交時,corePoolSize為0不創(chuàng)建核心線程,SynchronousQueue是一個不存儲元素的隊列,可以理解為隊里永遠是滿的,因此最終會創(chuàng)建非核心線程來執(zhí)行任務。對于非核心線程空閑60s時將被回收。因為Integer.MAX_VALUE非常大,可以認為是可以無限創(chuàng)建線程的,在資源有限的情況下容易引起OOM異常

Executors#newSingleThreadExecutor方法

public?static?ExecutorService?newFixedThreadPool(int?nThreads)?{
????return?new?ThreadPoolExecutor(nThreads,?nThreads,
??????????????????????????????????0L,?TimeUnit.MILLISECONDS,
??????????????????????????????????new?LinkedBlockingQueue<Runnable>());
}

SingleThreadExecutor是單線程線程池,只有一個核心線程

  • corePoolSize => 1,核心線程池的數(shù)量為1

  • maximumPoolSize => 1,只可以創(chuàng)建一個非核心線程

  • keepAliveTime => 0L

  • unit => 毫秒

  • workQueue => LinkedBlockingQueue

當一個任務提交時,首先會創(chuàng)建一個核心線程來執(zhí)行任務,如果超過核心線程的數(shù)量,將會放入隊列中,因為LinkedBlockingQueue是長度為Integer.MAX_VALUE的隊列,可以認為是無界隊列,因此往隊列中可以插入無限多的任務,在資源有限的時候容易引起OOM異常,同時因為無界隊列,maximumPoolSize和keepAliveTime參數(shù)將無效,壓根就不會創(chuàng)建非核心線程

Executors#newFixedThreadPool方法

public?static?ExecutorService?newFixedThreadPool(int?nThreads)?{
????return?new?ThreadPoolExecutor(nThreads,?nThreads,
??????????????????????????????????0L,?TimeUnit.MILLISECONDS,
??????????????????????????????????new?LinkedBlockingQueue<Runnable>());
}

FixedThreadPool是固定核心線程的線程池,固定核心線程數(shù)由用戶傳入

  • corePoolSize => 1,核心線程池的數(shù)量為1

  • maximumPoolSize => 1,只可以創(chuàng)建一個非核心線程

  • keepAliveTime => 0L

  • unit => 毫秒

  • workQueue => LinkedBlockingQueue

  • 它和SingleThreadExecutor類似,唯一的區(qū)別就是核心線程數(shù)不同,并且由于使用的是LinkedBlockingQueue,在資源有限的時候容易引起OOM異常

因此由上面的結(jié)論,可以看出使用Executors創(chuàng)建出的線程池是有可能引起內(nèi)存溢出的,所以我們并不推薦甚至不允許使用Executors來創(chuàng)建線程池

線程池的流程運轉(zhuǎn)原理

提交一個任務到線程池中,線程池的處理流程如下:

1、判斷線程池里的核心線程是否都在執(zhí)行任務,如果不是(核心線程空閑或者還有核心線程沒有被創(chuàng)建)則創(chuàng)建一個新的工作線程來執(zhí)行任務。如果核心線程都在執(zhí)行任務,則進入下個流程。

2、線程池判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列里。如果工作隊列滿了,則進入下個流程。

3、判斷線程池里的線程是否都處于工作狀態(tài)[線程數(shù)量是否達到最大值],如果沒有,則創(chuàng)建一個新的工作線程來執(zhí)行任務。如果已經(jīng)滿了,則交給飽和策略來處理這個任務。

面試必問的Java 線程池原理及最佳實踐

image-20210627171512964

其他的參數(shù)都比較容易理解,所以我們來著重看下拒絕策略handler這個參數(shù),其類型為RejectedExecutionHandler,當線程池達到最大值并且線程數(shù)也達到最大值時才會工作,當隊列和線程池都滿了,說明線程池處于飽和狀態(tài),那么必須對新提交的任務采用一種特殊的策略來進行處理。這個策略默認配置是AbortPolicy,表示無法處理新的任務而拋出異常。JAVA提供了4種策略:

  • AbortPolicy:直接拋出異常

  • CallerRunsPolicy:只用調(diào)用所在的線程運行任務

  • DiscardOldestPolicy:丟棄隊列里最近的一個任務,并執(zhí)行當前任務。

  • DiscardPolicy:不處理,丟棄掉。

面試必問的Java 線程池原理及最佳實踐

image-20210627174730111

當然我們上面也說了handler是一個RejectedExecutionHandler的類型,所以我們也可以實現(xiàn)這個接口來創(chuàng)建一個我們自己的拒絕策略

自定義拒絕策略

拒絕策略是一個接口,其設計如下:

public?interface?RejectedExecutionHandler?{
????void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?executor);
}

用戶可以通過實現(xiàn)這個接口去定制拒絕策略,在手動配置線程池時的構(gòu)造函數(shù)傳入或者通過方法setRejectedExecutionHandler?在線程池運行期間改變拒絕任務的策略。

????public?ThreadPoolExecutor(int?corePoolSize,
??????????????????????????????int?maximumPoolSize,
??????????????????????????????long?keepAliveTime,
??????????????????????????????TimeUnit?unit,
??????????????????????????????BlockingQueue<Runnable>?workQueue,
??????????????????????????????ThreadFactory?threadFactory,
??????????????????????????????RejectedExecutionHandler?handler)?{
??//省略其他代碼
????????this.handler?=?handler;
????}

????public?void?setRejectedExecutionHandler(RejectedExecutionHandler?handler)?{
????????if?(handler?==?null)
????????????throw?new?NullPointerException();
????????this.handler?=?handler;
????}
public?class?AbortPolicyWithReport?extends?ThreadPoolExecutor.AbortPolicy?{

????protected?static?final?Logger?logger?=?LoggerFactory.getLogger(AbortPolicyWithReport.class);
????
????private?final?String?threadName;
????
????private?final?URL?url;
????
????private?static?volatile?long?lastPrintTime?=?0;
????
????private?static?Semaphore?guard?=?new?Semaphore(1);
????
????public?AbortPolicyWithReport(String?threadName,?URL?url)?{
????????this.threadName?=?threadName;
????????this.url?=?url;
????}
????
????@Override
????public?void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?e)?{
????????String?msg?=?String.format("Thread?pool?is?EXHAUSTED!"?+
?????????????????????"?Thread?Name:?%s,?Pool?Size:?%d?(active:?%d,?core:?%d,?max:?%d,?largest:?%d),?Task:?%d?(completed:?%d),"?+
?????????????????????"?Executor?status:(isShutdown:%s,?isTerminated:%s,?isTerminating:%s),?in?%s://%s:%d!",
?????????????????????threadName,?e.getPoolSize(),?e.getActiveCount(),?e.getCorePoolSize(),?e.getMaximumPoolSize(),?e.getLargestPoolSize(),
?????????????????????e.getTaskCount(),?e.getCompletedTaskCount(),?e.isShutdown(),?e.isTerminated(),?e.isTerminating(),
?????????????????????url.getProtocol(),?url.getIp(),?url.getPort());
????????logger.warn(msg);
????????dumpJStack();
????????throw?new?RejectedExecutionException(msg);
????}
????
????private?void?dumpJStack()?{
???????//......
????}
}

拒絕策略的執(zhí)行

從調(diào)用execute()方法,經(jīng)過一系列判斷,當該任務被判斷需要被被拒絕后,會接著執(zhí)行reject(command)?,最終就會執(zhí)行具體實現(xiàn)RejectedExecutionHandler接口的rejectedExecution(r,executor)?方法了。

????final?void?reject(Runnable?command)?{
????????handler.rejectedExecution(command,?this);
????}

3. 線程池結(jié)合SpringBoot實戰(zhàn)(結(jié)合項目)

SpringBoot使用線程池我們常見的有兩種方式:

  1. 使用默認的線程池@Async

  2. 使用自定義的線程池

方式一:通過@Async注解調(diào)用

第一步:在Application啟動類上面加上@EnableAsync

@SpringBootApplication
@EnableAsync
public?class?ThreadpoolApplication?{
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(ThreadpoolApplication.class,?args);
????}
}

第二步:在需要異步執(zhí)行的方法上加上@Async注解

@Service
public?class?AsyncTest?{
????protected?final?Logger?logger?=?LoggerFactory.getLogger(this.getClass());
????@Async
????public?void?hello(String?name){
?????//這里使用logger?方便查看執(zhí)行的線程是什么
????????logger.info("異步線程啟動?started."+name);??
????}
}

第三步:測試類進行測試驗證

????@Autowired
????AsyncTest?asyncTest;
????@Test
????void?contextLoads()?throws?InterruptedException?{
????????asyncTest.hello("afsasfasf");
????????//一定要休眠?不然主線程關閉了,子線程還沒有啟動
????????Thread.sleep(1000);
????}

查看打印的日志:

INFO 2276 --- [ ? ? ? ? ? main] c.h.s.t.t.ThreadpoolApplicationTests ? ? : Started ThreadpoolApplicationTests in 3.003 seconds (JVM running for 5.342)

INFO 2276 --- [ ? ? ? ? task-1] c.h.s.threadpool.threadpool.AsyncTest ? ?: 異步線程啟動 started.afsasfasf

可以清楚的看到新開了一個task-1的線程執(zhí)行任務。驗證成功?。。?/p>

***注意:***@Async注解失效常景

方式二:使用自定義的線程池

在默認配置信息里面是沒有線程池的拒絕策略設置的方法的,如果需要更換拒絕策略就需要自定義線程池,并且如果項目當中需要多個自定義的線程池,又要如何進行管理呢?

自定義Configuration

第一步:創(chuàng)建一個ThreadPoolConfig 先只配置一個線程池,并設置拒絕策略為CallerRunsPolicy

@Configuration
public?class?ThreadPoolConfig?{

????@Bean("taskExecutor")
????public?Executor?taskExecutor()?{
????????ThreadPoolTaskExecutor?taskExecutor?=?new?ThreadPoolTaskExecutor();
????????//設置線程池參數(shù)信息
????????taskExecutor.setCorePoolSize(10);
????????taskExecutor.setMaxPoolSize(50);
????????taskExecutor.setQueueCapacity(200);
????????taskExecutor.setKeepAliveSeconds(60);
????????taskExecutor.setThreadNamePrefix("myExecutor--");
????????taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
????????taskExecutor.setAwaitTerminationSeconds(60);
????????//修改拒絕策略為使用當前線程執(zhí)行
????????taskExecutor.setRejectedExecutionHandler(new?ThreadPoolExecutor.CallerRunsPolicy());
????????//初始化線程池
????????taskExecutor.initialize();
????????return?taskExecutor;
????}
}

然后執(zhí)行之前寫的測試代碼發(fā)現(xiàn),使用的線程池已經(jīng)變成自定義的線程池了。

INFO 12740 --- [ ?myExecutor--2] c.h.s.t.t.ThreadpoolApplicationTests ? ? : threadPoolTaskExecutor 創(chuàng)建線程

INFO 12740 --- [ ?myExecutor--1] c.h.s.threadpool.threadpool.AsyncTest ? ?: 異步線程啟動 started.async注解創(chuàng)建

第二步:如果配置有多個線程池,該如何指定線程池呢?

@Configuration
public?class?ThreadPoolConfig?{

???????@Bean("taskExecutor")
????public?Executor?taskExecutor()?{
????????ThreadPoolTaskExecutor?taskExecutor?=?new?ThreadPoolTaskExecutor();
????????//設置線程池參數(shù)信息
????????taskExecutor.setCorePoolSize(10);
????????taskExecutor.setMaxPoolSize(50);
????????taskExecutor.setQueueCapacity(200);
????????taskExecutor.setKeepAliveSeconds(60);
????????taskExecutor.setThreadNamePrefix("myExecutor--");
????????taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
????????taskExecutor.setAwaitTerminationSeconds(60);
????????//修改拒絕策略為使用當前線程執(zhí)行
????????taskExecutor.setRejectedExecutionHandler(new?ThreadPoolExecutor.CallerRunsPolicy());
????????//初始化線程池
????????taskExecutor.initialize();
????????return?taskExecutor;
????}

????@Bean("poolExecutor")
????public?Executor?poolExecutor()?{
????????ThreadPoolTaskExecutor?taskExecutor?=?new?ThreadPoolTaskExecutor();
????????//設置線程池參數(shù)信息
????????taskExecutor.setCorePoolSize(10);
????????taskExecutor.setMaxPoolSize(50);
????????taskExecutor.setQueueCapacity(200);
????????taskExecutor.setKeepAliveSeconds(60);
????????taskExecutor.setThreadNamePrefix("myExecutor2--");
????????taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
????????taskExecutor.setAwaitTerminationSeconds(60);
????????//修改拒絕策略為使用當前線程執(zhí)行
????????taskExecutor.setRejectedExecutionHandler(new?ThreadPoolExecutor.CallerRunsPolicy());
????????//初始化線程池
????????taskExecutor.initialize();
????????return?taskExecutor;
????}

????@Bean("taskPoolExecutor")
????public?Executor?taskPoolExecutor()?{
????????ThreadPoolTaskExecutor?taskExecutor?=?new?ThreadPoolTaskExecutor();
????????//設置線程池參數(shù)信息
????????taskExecutor.setCorePoolSize(10);
????????taskExecutor.setMaxPoolSize(50);
????????taskExecutor.setQueueCapacity(200);
????????taskExecutor.setKeepAliveSeconds(60);
????????taskExecutor.setThreadNamePrefix("myExecutor3--");
????????taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
????????taskExecutor.setAwaitTerminationSeconds(60);
????????//修改拒絕策略為使用當前線程執(zhí)行
????????taskExecutor.setRejectedExecutionHandler(new?ThreadPoolExecutor.CallerRunsPolicy());
????????//初始化線程池
????????taskExecutor.initialize();
????????return?taskExecutor;
????}
}

執(zhí)行測試類,直接報錯說找到多個類,不知道加載哪個類:

No qualifying bean of type 'org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor' available: expected single matching bean but found 3: taskExecutor,taskPoolExecutor

由于測試類當中是這樣自動注入的:

@Autowired
ThreadPoolTaskExecutor?threadPoolTaskExecutor;?

考慮到@Autowired 以及@Resource兩個注入時的存在多個類如何匹配問題,然后發(fā)現(xiàn)只要我們在注入時指定具體的bean就會調(diào)用對應的線程池?。?!

即修改測試類如下:

????@Autowired
????AsyncTest?asyncTest;
????@Autowired
????ThreadPoolTaskExecutor?poolExecutor;?//會去匹配?@Bean("poolExecutor")?這個線程池
????@Test
????void?contextLoads()?throws?InterruptedException?{
????????asyncTest.hello("async注解創(chuàng)建");
????????//一定要休眠?不然主線程關閉了,子線程還沒有啟動
????????poolExecutor.submit(new?Thread(()->{
????????????logger.info("threadPoolTaskExecutor?創(chuàng)建線程");
????????}));
????????Thread.sleep(1000);
????}

最后得到如下信息:

INFO 13636 --- [ myExecutor2--1] c.h.s.t.t.ThreadpoolApplicationTests ? ? : threadPoolTaskExecutor 創(chuàng)建線程 INFO 13636 --- [ ?myExecutor--1] c.h.s.threadpool.threadpool.AsyncTest ? ?: 異步線程啟動 started.async注解創(chuàng)建

注意:如果是使用的@Async注解,只需要在注解里面指定bean的名稱就可以切換到對應的線程池去了。如下所示:

?@Async("taskPoolExecutor")
????public?void?hello(String?name){
????????logger.info("異步線程啟動?started."+name);
????}

注意:如果有多個線程池,但是在@Async注解里面沒有指定的話,會默認加載第一個配置的線程池

submit和executor區(qū)別

execute和submit都屬于線程池的方法,execute只能提交Runnable類型的任務,而submit既能提交Runnable類型任務也能提交Callable類型任務。

execute會直接拋出任務執(zhí)行時的異常,submit會吃掉異常,可通過Future的get方法[會阻塞]將任務執(zhí)行時的異常重新拋出。

execute所屬頂層接口是Executor,submit所屬頂層接口是ExecutorService,實現(xiàn)類ThreadPoolExecutor重寫了execute方法,抽象類AbstractExecutorService重寫了submit方法。

submit和execute由于參數(shù)不同有四種實現(xiàn)形式,如下所示,本文主要研究這四種形式在各自使用場景下的區(qū)別和聯(lián)系

這種提交的方式會返回一個Future對象,這個Future對象代表這線程的執(zhí)行結(jié)果
當主線程調(diào)用Future的get方法的時候會獲取到從線程中返回的結(jié)果數(shù)據(jù)。
如果在線程的執(zhí)行過程中發(fā)生了異常,get會獲取到異常的信息。
<T>?Future<T>?submit(Callable<T>?task);

當線程正常結(jié)束的時候調(diào)用Future的get方法會返回result對象,當線程拋出異常的時候會獲取到對應的異常的信息。
<T>?Future<T>?submit(Runnable?task,?T?result);

提交一個Runable接口的對象,這樣當調(diào)用get方法的時候,如果線程執(zhí)行成功會直接返回null,如果線程執(zhí)行異常會返回異常的信息
Future<?>?submit(Runnable?task);

void?execute(Runnable?command);

execute提交的方式只能提交一個Runnable的對象,且該方法的返回值是void,也即是提交后如果線程運行后,和主線程就脫離了關系了,當然可以設置一些變量來獲取到線程的運行結(jié)果。并且當線程的執(zhí)行過程中拋出了異常通常來說主線程也無法獲取到異常的信息的,只有通過ThreadFactory主動設置線程的異常處理類才能感知到提交的線程中的異常信息。

4. 線程池原理

4.1 主要介紹線程池中線程復用原理

對于線程池的復用原理,可以簡單的用一句話概括:創(chuàng)建指定數(shù)量的線程并開啟,判斷當前是否有任務執(zhí)行,如果有則執(zhí)行任務。再通俗易懂一些:創(chuàng)建指定數(shù)量的線程并運行,重寫run方法,循環(huán)從任務隊列中取Runnable對象,執(zhí)行Runnable對象的run方法。

面試必問的Java 線程池原理及最佳實踐

image-20210702000757740

接下來開始手寫線程池吧,注意是簡易線程池,跟JDK自帶的線程池無法相提并論,在這里我省略了判斷當前線程數(shù)有沒有大于核心線程數(shù)的步驟,簡化成直接從隊列中取任務,對于理解原理來說已然足矣,代碼如下:

public?class?MyExecutorService?{
????/**
?????*?一直保持運行的線程
?????*/
????private?List<WorkThread>?workThreads;

????/*
?????*?任務隊列容器
?????*/
????private?BlockingDeque<Runnable>?taskRunables;
?????/*
?????*?線程池當前是否停止
?????*/
????private?volatile?boolean?isWorking?=?true;

????public?MyExecutorService(int?workThreads,?int?taskRunables)?{
????????this.workThreads?=?new?ArrayList<>();
????????this.taskRunables?=?new?LinkedBlockingDeque<>(taskRunables);
????????//直接運行核心線程
????????for?(int?i?=?0;?i?<?workThreads;?i++)?{
????????????WorkThread?workThread?=?new?WorkThread();
????????????workThread.start();
????????????this.workThreads.add(workThread);
????????}
????}
????
????/**
?????*?WorkThread累,線程池的任務類,類比JDK的worker
?????*/
????class?WorkThread?extends?Thread?{
????????@Override
????????public?void?run()?{
????????????while?(isWorking?||?taskRunables.size()?!=?0)?{
????????????????//獲取任務
????????????????Runnable?task?=?taskRunables.poll();
????????????????if?(task?!=?null)?{
????????????????????task.run();
????????????????}
????????????}
????????}
????}
????//執(zhí)行execute,jdk中會存在各種判斷,這里省略了
????public?void?execute(Runnable?runnable)?{
????????//把任務加入隊列
????????taskRunables.offer(runnable);
????}

????//停止線程池
????public?void?shutdown()?{
????????this.isWorking?=?false;
????}
}

測試

//測試自定義的線程池
public?static?void?main(String[]?args)?{
????MyExecutorService?myExecutorService?=?new?MyExecutorService(3,?6);
????//運行8次
????for?(int?i?=?0;?i?<?8;?i++)?{
????????myExecutorService.execute(()?->?{
????????????System.out.println(Thread.currentThread().getName()?+?"task?begin");
????????});
????}
????myExecutorService.shutdown();
}

總結(jié)

通過以上分析并手寫線程池,我們應該已經(jīng)基本理解了線程池的復用機制原理,實際上JDK的實現(xiàn)機制遠比我們手寫的要復雜的多,主要有以下兩點,可以讓我們進一步加深理解:

  1. 當有新任務來的時候,首先判斷當前的線程數(shù)有沒有超過核心線程數(shù),如果沒超過則直接新建一個線程來執(zhí)行新的任務,如果超過了則判斷緩存隊列有沒有滿,沒滿則將新任務放進緩存隊列中,如果隊列已滿并且線程池中的線程數(shù)已經(jīng)達到了指定的最大線程數(shù),那就根據(jù)相應的策略拒絕任務,默認為拋異常。

  2. 當緩存隊列中的任務都執(zhí)行完畢后,線程池中的線程數(shù)如果大于核心線程數(shù)并且已經(jīng)超過了指定的存活時間(存活時間通過隊列的poll方法傳入,如果指定時間內(nèi)沒有獲取到任務,則break退出,線程運行結(jié)束),就銷毀多出來的線程,直到線程池中的線程數(shù)等于核心線程數(shù)。此時剩余的線程會一直處于阻塞狀態(tài),等待新的任務到來。

有興趣可以看這塊的源碼,大致的思想就是:

首先,線程池會有一個管理任務的隊列,這個任務隊列里存放的就是各種任務,線程池會一直不停循環(huán)的去查看消息隊里有沒有接到任務,如果沒有,則繼續(xù)循環(huán),如果有了則開始創(chuàng)建線程,如果給這個線程池設定的容量是10個線程,那么當有任務的時候就會調(diào)用創(chuàng)建線程的函數(shù)方法去根據(jù)當前任務總數(shù)量依次創(chuàng)建線程(這里創(chuàng)建線程的函數(shù)方法都是你提前些好了的),線程中會寫好循環(huán)獲取任務隊列里任務的邏輯、判斷是否銷毀該線程的邏輯、進入等待的邏輯,這樣線程一旦創(chuàng)建出來就會循環(huán)的去查詢?nèi)蝿贞犃欣锏娜蝿?,拿到任務后就?zhí)行,執(zhí)行任務完畢后判斷是否銷毀該線程,如果不銷毀就進入等待(sleep),等待時間過后繼續(xù)查詢消息是否有任務,如此循環(huán),直到邏輯判斷需要銷毀該線程為止(一般都是根據(jù)設定時間去判斷是否銷毀,例如在線程創(chuàng)建的時候設置一個計時器去控制,如果180秒都沒有接到新的任務,則銷毀該線程) 。?文章來源地址http://www.zghlxwxcb.cn/news/detail-429074.html

到了這里,關于面試必問的Java 線程池原理及最佳實踐的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權(quán),不承擔相關法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • Vue雙向數(shù)據(jù)綁定原理(面試必問)

    Vue雙向數(shù)據(jù)綁定原理(面試必問)

    1、前端面試題庫 ( 面試必備) ? ? ? ? ? ? 推薦:★★★★★ 地址:前端面試題庫 ? vue.js是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應的監(jiān)聽回調(diào)來渲染視圖。 具體步驟

    2023年04月08日
    瀏覽(20)
  • 深入理解 Java 多線程、Lambda 表達式及線程安全最佳實踐

    線程使程序能夠通過同時執(zhí)行多個任務而更有效地運行。 線程可用于在不中斷主程序的情況下在后臺執(zhí)行復雜的任務。 創(chuàng)建線程 有兩種創(chuàng)建線程的方式。 擴展Thread類 可以通過擴展Thread類并覆蓋其run()方法來創(chuàng)建線程: 實現(xiàn)Runnable接口 另一種創(chuàng)建線程的方式是實現(xiàn)Runnable接口

    2024年03月15日
    瀏覽(29)
  • 一篇文章,輕松拿捏大廠必問的HashMap源碼分析

    一篇文章,輕松拿捏大廠必問的HashMap源碼分析

    目錄 一,JDK8之后HashMap的新特性 二,hashMap源碼屬性解讀 (一),默認初始化容量數(shù)量:16 (二),最大數(shù)組容量:2^30 (三),默認負載因子:0.75f (四),觸發(fā)樹化條件1,鏈表閾值: (五),解樹化的閾值: ?(六),觸發(fā)樹化條件二,hash桶閾值(數(shù)組元素個數(shù)): 三

    2023年04月08日
    瀏覽(32)
  • SpringBoot實踐(四十三):整理面試常問的問題

    1、Spring和springBoot和SpringCloud分別是什么及區(qū)別; Spring是一個框架,包含很多模塊,比如core,jdbc,dao,mvc,國際化等等; SpringBoot可以理解是spring的腳手架,快速地使用Spring進行集成開發(fā),把spring的多個模塊都集成在內(nèi),提供很簡單的配置方式使用; SpringCloud是1個微服務框架

    2024年02月06日
    瀏覽(22)
  • 【面試精講】Java線程6種狀態(tài)和工作原理詳解,Java創(chuàng)建線程的4種方式

    【面試精講】Java線程6種狀態(tài)和工作原理詳解,Java創(chuàng)建線程的4種方式

    Java線程6種狀態(tài)和工作原理詳解,Java創(chuàng)建線程的4種方式 一、Java線程的六種狀態(tài) 二、Java線程是如何工作的? 三、BLOCKED 和 WAITING 的區(qū)別 四、start() 和 run() 源碼分析 五、Java創(chuàng)建線程的所有方式和代碼詳解 1. 繼承Thread類 2. 實現(xiàn)Runnable接口 3. 實現(xiàn)Callable接口與FutureTask 4. 使用線

    2024年03月13日
    瀏覽(22)
  • java八股文面試[多線程]——Synchronized的底層實現(xiàn)原理

    java八股文面試[多線程]——Synchronized的底層實現(xiàn)原理

    筆試:畫出Synchronized 線程狀態(tài)流轉(zhuǎn) 實現(xiàn)原理圖 synchronized解決的是多個線程之間訪問資源的同步性,synchronized 翻譯為中文的意思是 同步 ,也稱之為”同步鎖“。 synchronized的作用是保證在 同一時刻 , 被修飾的代碼塊或方法只會有一個線程執(zhí)行,以達到保證并發(fā)安全的

    2024年02月10日
    瀏覽(25)
  • java八股文面試[多線程]——ThreadLocal底層原理和使用場景

    java八股文面試[多線程]——ThreadLocal底層原理和使用場景

    源碼分析: ThreadLocal中定義了ThreadLocalMap靜態(tài)內(nèi)部類,該內(nèi)部類中又定義了Entry內(nèi)部類。 ThreadLocalMap定了 Entry數(shù)組。 Set方法: Get方法: Thread中定義了兩個ThreaLocalMap成員變量: Spring使用ThreadLocal解決線程安全問題? 我們知道在一般情況下,只有 無狀態(tài)的Bean 才可以在多線程環(huán)

    2024年02月10日
    瀏覽(24)
  • 面試最常被問的 Java 后端題目及參考答案

    面試最常被問的 Java 后端題目及參考答案

    一、Java 基礎篇 1. Object 有哪些常用方法?大致說一下每個方法的含義 2. Java 創(chuàng)建對象有幾種方式? 3. 獲取一個類對象的方式有哪些? 4. ArrayList 和 LinkedList 的區(qū)別有哪些? 5. 用過 ArrayList 嗎?說一下它有什么特點? 6. 有數(shù)組了為什么還要搞個 ArrayList 呢? 7. 說說什么是 fai

    2024年02月12日
    瀏覽(20)
  • Qt5.14.2 Qt多線程實戰(zhàn)演練,全面掌握線程同步和線程池最佳實踐

    多線程編程是每個開發(fā)者必須掌握的基本能力之一。在上一篇文章中,我們學習了Qt多線程編程的理論知識。本文將切入實戰(zhàn),提供多個案例代碼,幫助你徹底掌握Qt的多線程編程實踐技巧。 案例1: 使用QThread執(zhí)行耗時任務 這個案例演示了如何通過繼承QThread和重寫run()函數(shù),在

    2024年03月20日
    瀏覽(25)
  • 云計算: 從基礎架構(gòu)原理到最佳實踐

    作者:禪與計算機程序設計藝術 云計算(Cloud computing)是一種新型的網(wǎng)絡服務模型,通過將應用程序、數(shù)據(jù)、服務和硬件資源通過互聯(lián)網(wǎng)提供給用戶,從而實現(xiàn)IT基礎設施和業(yè)務軟件部署、遷移、管理和運營的方式?;谠朴嬎愕能浖罩饕譃槿齻€層次:基礎設施即服務

    2024年02月08日
    瀏覽(31)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包