数值分析 使用c语言 源码_分析源码,学会正确使用 Java 线程池
在日常的開發(fā)工作當中,線程池往往承載著一個應(yīng)用中最重要的業(yè)務(wù)邏輯,因此我們有必要更多地去關(guān)注線程池的執(zhí)行情況,包括異常的處理和分析等。本文主要聚焦在如何正確使用線程池上,以及提供一些實用的建議。文中會稍微涉及到一些線程池實現(xiàn)原理方面的知識,但是不會過多展開。
線程池的異常處理
UncaughtExceptionHandler
我們都知道Runnable接口中的run方法是不允許拋出異常的,因此派生出這個線程的主線程可能無法直接獲得該線程在執(zhí)行過程中的異常信息。如下例:
為什么會這樣呢?其實我們看一下Thread中的源碼就會發(fā)現(xiàn),Thread在執(zhí)行過程中如果遇到了異常,會先判斷當前線程是否有設(shè)置UncaughtExceptionHandler,如果沒有,則會從線程所在的ThreadGroup中獲取。
注意:每個線程都有自己的ThreadGroup,即使你沒有指定,并且它實現(xiàn)了UncaughtExceptionHandler接口。
我們看下ThreadGroup中默認的對UncaughtExceptionHandler接口的實現(xiàn):
這個ThreadGroup如果有父ThreadGroup,則調(diào)用父ThreadGroup的uncaughtException,否則調(diào)用全局默認的Thread.DefaultUncaughtExceptionHandler,如果全局的handler也沒有設(shè)置,則只是簡單地將異常信息定位到System.err中,這就是為什么我們應(yīng)當在創(chuàng)建線程的時候,去實現(xiàn)它的UncaughtExceptionHandler接口的原因,這么做可以讓你更方便地去排查問題。
通過execute提交任務(wù)給線程池
回到線程池這個話題,如果我們向線程池提交的任務(wù)中,沒有對異常進行try...catch處理,并且運行的時候出現(xiàn)了異常,那會對線程池造成什么影響呢?答案是沒有影響,線程池依舊可以正常工作,但是異常卻被吞掉了。這通常來說不是一個好事情,因為我們需要拿到原始的異常對象去分析問題。
那么怎樣才能拿到原始的異常對象呢?我們從線程池的源碼著手開始研究這個問題。當然網(wǎng)上關(guān)于線程池的源碼解析文章有很多,這里限于篇幅,直接給出最相關(guān)的部分代碼:
這個方法就是真正去執(zhí)行提交給線程池的任務(wù)的代碼。
這里我們略去其中不相關(guān)的邏輯,重點關(guān)注第19行到第32行的邏輯,其中第23行是真正開始執(zhí)行提交給線程池的任務(wù),那么第20行是干什么的呢?其實就是在執(zhí)行提交給線程池的任務(wù)之前可以做一些前置工作,同樣的,我們看到第31行,這個是在執(zhí)行完提交的任務(wù)之后,可以做一些后置工作。
beforeExecute這個我們暫且不管,重點關(guān)注下afterExecute這個方法。我們可以看到,在執(zhí)行任務(wù)過程中,一旦拋出任何類型的異常,都會提交給afterExecute這個方法,然而查看線程池的源代碼我們可以發(fā)現(xiàn),默認的afterExecute是個空實現(xiàn),因此,我們有必要繼承ThreadPoolExecutor去實現(xiàn)這個afterExecute方法。
看源碼我們可以發(fā)現(xiàn)這個afterExecute方法是protected類型的,從官方注釋上也可以看到,這個方法就是推薦子類去實現(xiàn)的。
當然,這個方法不能隨意去實現(xiàn),需要遵循一定的步驟,具體的官方注釋也有講,這里摘抄如下
那么通過這種方式,就可以將原先可能被線程池吞掉的異常成功捕獲到,從而便于排查問題。
但是這里還有個小問題,我們注意到在runWorker方法中,執(zhí)行task.run();語句之后,各種類型的異常都被拋出了,那這些被拋出的異常去了哪里?事實上這里的異常對象最終會被傳入到Thread的dispatchUncaughtException方法中,源碼如下:
可以看到它會去獲取UncaughtExceptionHandler的實現(xiàn)類,然后調(diào)用其中的uncaughtException方法,這也就回到了我們上一小節(jié)所分析的UncaughtExceptionHandler實現(xiàn)的具體邏輯。那么為了拿到最原始的異常對象,除了實現(xiàn)UncaughtExceptionHandler接口之外,也可以考慮實現(xiàn)afterExecute方法。
通過submit提交任務(wù)到線程池
這個同樣很簡單,我們還是先回到submit方法的源碼:
這里的execute方法調(diào)用的是ThreadPoolExecutor中的execute方法,執(zhí)行邏輯跟通過execute提交任務(wù)到線程池是一樣的。我們先重點關(guān)注這里的newTaskFor方法,其源碼如下:
可以看到提交的Callable對象用FutureTask封裝起來了。我們知道最終會執(zhí)行到上述runWorker這個方法中,并且最核心的執(zhí)行邏輯就是task.run();這行代碼。我們知道這里的task其實是FutureTask類型,因此我們有必要看一下FutureTask中的run方法的實現(xiàn):
可以看到這其中跟異常相關(guān)的最關(guān)鍵的代碼就在第17行,也就是setException(ex);這個地方。我們看一下這個地方的實現(xiàn):
這里最關(guān)鍵的地方就是將異常對象賦值給了outcome,outcome是FutureTask中的成員變量,我們通過調(diào)用submit方法,拿到一個Future對象之后,再調(diào)用它的get方法,其中最核心的方法就是report方法,下面給出每個方法的源碼:
首先是get方法:
可以看到最終調(diào)用了report方法,其源碼如下:
上面是一些狀態(tài)判斷,如果當前任務(wù)不是正常執(zhí)行完畢,或者被取消的話,那么這里的x其實就是原始的異常對象,可以看到會被ExecutionException包裝。因此在你調(diào)用get方法時,可能會拋出ExecutionException異常,那么調(diào)用它的getCause方法就可以拿到最原始的異常對象了。
綜上所述,針對提交給線程池的任務(wù)可能會拋出異常這一問題,主要有以下兩種處理思路:
下面給出我個人創(chuàng)建線程池的一個示例,供大家參考:
BlockingQueue queue = new ArrayBlockingQueue<>(DEFAULT_QUEUE_SIZE); statisticsThreadPool = new ThreadPoolExecutor(DEFAULT_CORE_POOL_SIZE, DEFAULT_MAX_POOL_SIZE, 60, TimeUnit.SECONDS, queue, new ThreadFactoryBuilder() .setThreadFactory(new ThreadFactory() { private int count = 0; private String prefix = "StatisticsTask"; @Override public Thread newThread(Runnable r) { return new Thread(r, prefix + "-" + count++); } }).setUncaughtExceptionHandler((t, e) -> { String threadName = t.getName(); logger.error("statisticsThreadPool error occurred! threadName: {}, error msg: {}總結(jié)
以上是生活随笔為你收集整理的数值分析 使用c语言 源码_分析源码,学会正确使用 Java 线程池的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 幸福一家人微信网名68个
- 下一篇: java数组和list_Java中的数组