Java Review - 线程池资源一直不被释放案例源码分析
文章目錄
- 概述
- 問題復現
- 源碼分析
- 小結
概述
在日常開發中為了便于線程的有效復用,經常會用到線程池,然而使用完線程池后如果不調用shutdown關閉線程池,則會導致線程池資源一直不被釋放。
下面通過簡單的例子來說明該問題。
問題復現
下面通過一個例子說明如果不調用線程池對象的shutdown方法關閉線程池,則當線程池里面的任務執行完畢并且主線程已經退出后,JVM仍然存在。
import java.util.concurrent.*;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/11/20 23:33* @mark: show me the code , change the world*/ public class PoolShutDownTest {public static void main(String[] args) {// 異步執行業務1ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10,TimeUnit.MINUTES, new LinkedBlockingDeque<>(100));threadPoolExecutor.execute(()->{try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("業務1----模擬業務");});// 異步執行業務2ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.execute(()->System.out.println("業務2"));// 業務執行完成System.out.println("executor over");} }在如上代碼的主線程里面,首先 執行了異步業務, 使用線程池的一個線程執行異步操作,我們期望當主線程與異步業務執行完線程池里面的任務后整個JVM就會退出,但是執行結果卻如下所示。
左上的方塊說明JVM進程還沒有退出 ,這是什么情況呢?修改代碼代碼,在方法里面添加調用線程池的shutdown方法的代碼。
再次執行代碼你會發現JVM已經退出了,使用ps -eaf|grep java命令查看,發現Java進程已經不存在了,這說明只有調用了線程池的shutdown方法后,線程池任務執行完畢,線程池資源才會被釋放。
源碼分析
下面看為何會如此?大家或許還記得守護線程與用戶線程,JVM退出的條件是當前不存在用戶線程,而線程池默認的ThreadFactory創建的線程是用戶線程。
由如上代碼可知,線程池默認的ThreadFactory創建的都是用戶線程。而線程池里面的核心線程是一直存在的,如果沒有任務則會被阻塞,所以線程池里面的用戶線程一直存在。而shutdown方法的作用就是讓這些核心線程終止.
下面簡單看下shutdown的主要代碼。
/*** Initiates an orderly shutdown in which previously submitted* tasks are executed, but no new tasks will be accepted.* Invocation has no additional effect if already shut down.** <p>This method does not wait for previously submitted tasks to* complete execution. Use {@link #awaitTermination awaitTermination}* to do that.** @throws SecurityException {@inheritDoc}*/public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();// 設置線程池狀態為SHUTDOWNadvanceRunState(SHUTDOWN);// 中斷所有的空閑工作線程interruptIdleWorkers();onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {mainLock.unlock();}tryTerminate();}這里在shutdown方法里面設置了線程池的狀態為SHUTDOWN,并且設置了所有Worker空閑線程(阻塞到隊列的take()方法的線程)的中斷標志。
/*** Transitions runState to given target, or leaves it alone if* already at least the given target.** @param targetState the desired state, either SHUTDOWN or STOP* (but not TIDYING or TERMINATED -- use tryTerminate for that)*/private void advanceRunState(int targetState) {for (;;) {int c = ctl.get();if (runStateAtLeast(c, targetState) ||ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))break;}}那么下面來看在工作線程Worker里面是不是設置了中斷標志,然后它就會退出。
final void runWorker(Worker w) {.......try {while (task != null || (task = getTask()) != null) {....... } finally {.......}} private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?for (;;) {....// Check if queue empty only if necessary.// 代碼1 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}try {// 代碼2Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)return r;timedOut = true;} catch (InterruptedException retry) {timedOut = false;}}}在如上代碼中,在正常情況下如果隊列里面沒有任務,則工作線程被阻塞到代碼(2)等待從工作隊列里面獲取一個任務。
這時候如果調用線程池的shutdown命令(shutdown命令會中斷所有工作線程),則代碼(2)會拋出InterruptedException異常而返回,而這個異常被捕捉到了,所以繼續執行代碼(1),而執行shutdown時設置了線程池的狀態為SHUTDOWN,所以getTask方法返回了null,因而runWorker方法退出循環,該工作線程就退出了。
小結
我們這里通過一個簡單的使用線程池異步執行任務的案例介紹了使用完線程池后如果不調用shutdown方法,則會導致線程池的線程資源一直不會被釋放,并通過源碼分析了沒有被釋放的原因。
所以在日常開發中使用線程池后一定不要忘記調用shutdown方法關閉。
總結
以上是生活随笔為你收集整理的Java Review - 线程池资源一直不被释放案例源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Review - 创建线程和线
- 下一篇: Java Review - 线程池中使用