3.10 使用線程池時(shí)候當(dāng)程序結(jié)束時(shí)候記得調(diào)用shutdown關(guān)閉線程池
日常開(kāi)發(fā)中為了便于線程的有效復(fù)用述么,線程池是經(jīng)常會(huì)被用的工具几于,然而線程池使用完后如果不調(diào)用shutdown會(huì)導(dǎo)致線程池資源一直不會(huì)被釋放。下面通過(guò)簡(jiǎn)單例子來(lái)說(shuō)明該問(wèn)題。
3.10.1問(wèn)題復(fù)現(xiàn)
下面通過(guò)一個(gè)例子說(shuō)明當(dāng)不調(diào)用線程池對(duì)象的shutdown方法后圆裕,當(dāng)線程池里面的任務(wù)執(zhí)行完畢后主線程這個(gè)JVM不會(huì)退出。
public class TestShutDown {
static void asynExecuteOne() {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
public void run() {
System.out.println("--async execute one ---");
}
});
}
static void asynExecuteTwo() {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
public void run() {
System.out.println("--async execute two ---");
}
});
}
public static void main(String[] args) {
//(1)同步執(zhí)行
System.out.println("---sync execute---");
//(2)異步執(zhí)行操作one
asynExecuteOne();
//(3)異步執(zhí)行操作two
asynExecuteTwo();
//(4)執(zhí)行完畢
System.out.println("---execute over---");
}
}
如上代碼主線程里面首先同步執(zhí)行了操作(1)然后執(zhí)行操作(2)(3)荆几,操作(2)(3)使用線程池的一個(gè)線程執(zhí)行異步操作吓妆,我們期望當(dāng)主線程和操操作(2)(3)執(zhí)行完線程池里面的任務(wù)后整個(gè)JVM就會(huì)退出,但是執(zhí)行結(jié)果卻如下:
右上角紅色方塊說(shuō)明JVM進(jìn)程還沒(méi)有退出吨铸,Mac上執(zhí)行
ps -eaf|grep java
后發(fā)現(xiàn)Java進(jìn)程還是存在的行拢,這是什么情況那?修改操作(2)(3)在方法里面添加調(diào)用線程池的shutdown方法如下代碼:
static void asynExecuteOne() {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
public void run() {
System.out.println("--async execute one ---");
}
});
executor.shutdown();
}
static void asynExecuteTwo() {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
public void run() {
System.out.println("--async execute two ---");
}
});
executor.shutdown();
}
在執(zhí)行就會(huì)發(fā)現(xiàn)JVM已經(jīng)退出了诞吱,使用ps -eaf|grep java
后發(fā)現(xiàn)Java進(jìn)程以及不存在了舟奠,這說(shuō)明只有調(diào)用了線程池的shutdown方法后當(dāng)線程池任務(wù)執(zhí)行完畢后線程池資源才會(huì)釋放。
3.10.2問(wèn)題分析
下面看下為何如此那房维?大家或許還記得基礎(chǔ)篇講解的守護(hù)線程與用戶線程吧沼瘫,JVM退出的條件是當(dāng)前不存在用戶線程,而線程池默認(rèn)的ThreadFactory創(chuàng)建的線程是用戶線程咙俩,
static class DefaultThreadFactory implements ThreadFactory {
...
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
如上代碼可知線程池默認(rèn)的線程工廠創(chuàng)建創(chuàng)建的都是用戶線程耿戚。而線程池里面的核心線程是一直會(huì)存在的湿故,如果沒(méi)有任務(wù)則會(huì)阻塞,所以線程池里面的用戶線程一直會(huì)存在.而shutdown方法的作用就是讓這些核心線程終止膜蛔,下面在簡(jiǎn)單看下shutdown重要代碼:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
...
//設(shè)置線程池狀態(tài)為SHUTDOWN
advanceRunState(SHUTDOWN);
//中斷所有的工作線程
interruptIdleWorkers();
...
} finally {
mainLock.unlock();
}
...
}
可知shutdown里面設(shè)置了線程池狀態(tài)為SHUTDOWN坛猪,并且設(shè)置了所有工作線程的中斷標(biāo)志,那么下面在簡(jiǎn)單看下工作線程Worker里面是不是發(fā)現(xiàn)中斷標(biāo)志被設(shè)置了就會(huì)退出了皂股。
final void runWorker(Worker w) {
...
try {
while (task != null || (task = getTask()) != null) {
...
}
...
} finally {
...
}
}
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
...
//(1)
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
try {
//(2)
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
如上代碼正常情況下如果隊(duì)列里面沒(méi)有任務(wù)了墅茉,工作線程阻塞到代碼(2)等待從工工作隊(duì)列里面獲取一個(gè)任務(wù),這時(shí)候如果調(diào)用了線程池的shutdown命令而shutdown命令會(huì)中斷所有工作線程呜呐,所以代碼(2)會(huì)拋出處拋出InterruptedException異常而返回就斤,而這個(gè)異常被catch了,所以繼續(xù)執(zhí)行代碼(1)蘑辑,而shutdown時(shí)候設(shè)置了線程池的狀態(tài)為SHUTDOWN所以getTask方法返回了null战转,所以runWorker方法退出循環(huán),該工作線程就退出了以躯。
3.10.3 總結(jié)
本節(jié)通過(guò)一個(gè)簡(jiǎn)單的使用線程池異步執(zhí)行任務(wù)案例介紹了線程池使用完后要如果不調(diào)用shutdown會(huì)導(dǎo)致線程池的線程資源一直不會(huì)被釋放,然后通過(guò)源碼分析了沒(méi)有被釋放的原因啄踊。所以日常開(kāi)發(fā)中使用線程池的場(chǎng)景一定不要忘記了調(diào)用shutdown方法設(shè)置線程池狀態(tài)和中斷工作線程池
--------------------------------相約GitChat探討技術(shù)--------------------------------------
一忧设、常用開(kāi)源框架 Spring 擴(kuò)展接口揭秘(文章審核中)
評(píng)價(jià)一個(gè)框架是否優(yōu)秀,其中必有一點(diǎn)是看該框架是否留足了可擴(kuò)展的接口颠通。我們?cè)趯?shí)際做項(xiàng)目或者研發(fā)框架時(shí)址晕,很多情況下就是在框架留出的擴(kuò)展接口上進(jìn)行定制,所以很有必要對(duì)這些框架留出了哪些擴(kuò)展點(diǎn)顿锰,這些擴(kuò)展點(diǎn)是干啥用的有個(gè)心知肚明的了解谨垃。
本 Chat 將針對(duì) Spring 擴(kuò)展點(diǎn)進(jìn)行介紹,主要內(nèi)容包括:
對(duì) Spring 框架在容器刷新(Refresh 階段)硼控,創(chuàng)建 Bean(getBean)刘陶,容器銷毀(destory)階段中的擴(kuò)展接口進(jìn)行講解;
-
對(duì) Spring 中的 ContextLoaderListener 擴(kuò)展接口進(jìn)行講解牢撼,并講解 Webx 框架和 SpringMVC 框架如何使用它匙隔,從而讓 Tomcat 與應(yīng)用框架聯(lián)系起來(lái)。
## 二熏版、SpringBoot核心模塊原理分析Chat(文章審核中)
最近微服務(wù)很火纷责,SpringBoot 以其輕量級(jí),內(nèi)嵌 Web 容器撼短,一鍵啟動(dòng)再膳,方便調(diào)試等特點(diǎn)被越來(lái)越多的微服務(wù)實(shí)踐者所采用。然而知其然還要知其所以然曲横,本節(jié)就來(lái)講解 SpringBoot 的核心模塊的實(shí)現(xiàn)原理喂柒,這些內(nèi)容在面試的時(shí)候也是會(huì)被經(jīng)常問(wèn)到的:
spring-boot-load 模塊,正常情況下一個(gè)類加載器只能找到加載路徑的jar包里面當(dāng)前目錄或者文件類里面的*.class文件,SpringBoot 允許我們使用 java -jar archive.jar 運(yùn)行包含嵌套依賴 jar 的 jar 或者 war 文件胳喷,那么 SpringBoot 是如何實(shí)現(xiàn)的那湃番?
spring-boot-autoconfigure 模塊,Auto-configuration 是 SpringBoot 在 Spring 的基礎(chǔ)上提供的一個(gè)自動(dòng)掃描 jar 包里面指定注解的類并注入到 Spring 容器的功能組件吭露。
-
spring-boot 模塊吠撮,提供了一些特性用來(lái)支持 SpringBoot 中其它模塊。
歡迎長(zhǎng)按識(shí)別二維碼加入本chat
三讲竿、Java 類加載器揭秘Chat(文章已經(jīng)出爐)
類加載器作為 JVM 加載字節(jié)碼到內(nèi)存中的媒介泥兰,其重要性不言而喻,另外在職場(chǎng)面試時(shí)候也會(huì)被頻繁的問(wèn)道题禀,了解類加載器的原理鞋诗,能靈活的自定義類加載器去實(shí)現(xiàn)自己的功能顯得尤為重要。
主要內(nèi)容:
講解 Java 中自帶的三種類加載器迈嘹,以及構(gòu)造原理
講解類加載器原理
講解一種特殊的與線程相關(guān)類加載器
講解 Tomcat 框架中多級(jí)類加載器的實(shí)現(xiàn)原理
-
講解如何自定義類加載器實(shí)現(xiàn)模塊隔離
歡迎長(zhǎng)按識(shí)別二維碼加入本chat