Java — 慎用Executors类中newFixedThreadPool()和newCachedThreadPool()
文章目錄
- Executors.newCachedThreadPool()
- 源碼
- 分析
- Executors.newFixedThreadPool()
- 源碼
- 分析
- 避坑指南
- 自定義線程池
在一些要求嚴格的公司,一般都明令禁止是使用Excutor提供的newFixedThreadPool()和newCachedThreadPool()直接創(chuàng)建線程池來操作線程,既然被禁止,那么就會有被禁止的道理,我們先來看一下之所以會被禁止的原因。
Executors.newCachedThreadPool()
源碼
/*** Creates a thread pool that creates new threads as needed, but* will reuse previously constructed threads when they are* available. These pools will typically improve the performance* of programs that execute many short-lived asynchronous tasks.* Calls to {@code execute} will reuse previously constructed* threads if available. If no existing thread is available, a new* thread will be created and added to the pool. Threads that have* not been used for sixty seconds are terminated and removed from* the cache. Thus, a pool that remains idle for long enough will* not consume any resources. Note that pools with similar* properties but different details (for example, timeout parameters)* may be created using {@link ThreadPoolExecutor} constructors.** @return the newly created thread pool*/public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}大致意思就是,通過該方法會創(chuàng)建一個線程池,當你執(zhí)行一個任務,并且線程池中不存在可用的已構造好的線程時,它就會創(chuàng)建一個新線程,否則它會優(yōu)先復用已有的線程,當線程未被使用時,默認 6 秒后被移除。
里面有一句話:
These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks.
這些線程池可以很明顯的提升那些短期存活的異步任務的執(zhí)行效率
很明顯,官方標注他適合處理業(yè)務簡單、耗時短的任務,這是為什么呢?
我們接著看 ThreadPoolExecutor 構造方法的描述:
分析
結合ThreadPoolExecutor 構造方法的描述,我們可以知道,當我們調用newCachedThreadPool()方法的時候,它會創(chuàng)建一個核心線程數為 0 ,最大線程數為Integer上限,無用線程存活時間為 6 秒的線程池。
這意味著當我們需要在多線程中執(zhí)行復雜業(yè)務時,它會瘋狂的創(chuàng)建線程,因為其他線程中的業(yè)務并未執(zhí)行完。
例如下列代碼:
模擬瞬間創(chuàng)建100000000十萬個任務,且每個任務需要等待一秒鐘,會發(fā)現電腦內存使用率迅速增加并一直持續(xù)到 OOM。
Executors.newFixedThreadPool()
我們再來看一下源碼
源碼
/*** Creates a thread pool that reuses a fixed number of threads* operating off a shared unbounded queue. At any point, at most* {@code nThreads} threads will be active processing tasks.* If additional tasks are submitted when all threads are active,* they will wait in the queue until a thread is available.* If any thread terminates due to a failure during execution* prior to shutdown, a new one will take its place if needed to* execute subsequent tasks. The threads in the pool will exist* until it is explicitly {@link ExecutorService#shutdown shutdown}.** @param nThreads the number of threads in the pool* @return the newly created thread pool* @throws IllegalArgumentException if {@code nThreads <= 0}*/ public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()); }大致描述是:創(chuàng)建一個由固定數量線程并在共享無界隊列上運行的線程池,在任何時候都最多只有nThreads個線程存在并執(zhí)行任務。
如果在任務提交時,所有線程都在工作中,則會將該任務放入到隊列中等待,直到有可用的線程。如果某個線程在執(zhí)行過程中出現異常,那么這個線程會終止,并且會有一個新的線程代替它進行后續(xù)的工作,線程池中的線程會一直存在直到線程池被明確的停止掉。
分析
通過源碼我們可以看出,該方法創(chuàng)建一個固定核心線程數和線程池大小的線程池,并且核心數等于最大線程數。看起來好像沒有類似newCachedThreadPool無限創(chuàng)建線程的情況,但是在他的描述中有一點很引人注意,
operating off a shared unbounded queue
操作一個共享無界的隊列
通過查看newFixedThreadPool()在創(chuàng)建線程池時傳入的隊列 new LinkedBlockingQueue()
會發(fā)現,這個隊列的最大長度時Integer.MAX_VALUE,這就意味著,未能及時執(zhí)行的任務都將添加到這個隊列里面
隨著任務的增加,這個隊列所占用的內存將越來越多。最終導致OOM也是遲早的事情。
避坑指南
對于線程池這種東西,其實讓我們自己去控制是最好的,我們可以通過實現自定義的線程池提供線程,不僅可以定制化的獲取線程執(zhí)行過程中的狀態(tài)等信息,還能根據不同的任務使用不同的線程池。
例如,一條簡單的 查詢操作 和 文件讀取操作 就應該放在不同的線程池里面
因為如果兩種任務在同一個線程池里面,文件操作本身就是耗時的,它占用了線程之后會導致查詢操作等待或者直接被丟棄(取決于自定義線程池任務添加時的規(guī)則),這樣嚴重影響了查詢性能。
自定義線程池
//隊列長度為100BlockingQueue<Runnable> blockqueue = new LinkedBlockingQueue<Runnable>(100) {/*** 這里重寫offer方法* 在接收到新的任務時,會先加入到隊列中,當隊列滿了之后,才會創(chuàng)建新的線程 直到達到線程池的最大線程數* 我們現在需要接收到新任務時,優(yōu)先將線程數擴容到最大數,后續(xù)任務再放入到隊列中* 加入隊列會調用 offer方法 ,我們直接返回false,制造隊列已滿的假象*/@Overridepublic boolean offer(Runnable e) {return false;}};ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 10,10, TimeUnit.SECONDS,blockqueue , new ThreadFactoryBuilder().setNameFormat("mypool-%d").get(), (r, executor) -> {/*** 這里拒絕策略,被拒絕的任務會走該方法 及沒添加到隊列中,且沒有獲取到線程的任務* 因為我們設置的隊列中 offer方法固定返回false*/try {//如果允許該任務執(zhí)行但是不阻塞,及如果進不了隊列就放棄,我們可以調用 offer 的另一個多參的方法if (!executor.getQueue().offer(r, 0, TimeUnit.SECONDS)) {throw new RejectedExecutionException("ThreadPool queue full, failed to offer " + r.toString());}//如果我們需要讓任務一定要執(zhí)行,及足協(xié)而等待進入隊列,可以使用putexecutor.getQueue().put(r)} catch (InterruptedException e) {Thread.currentThread().interrupt();}});總結
以上是生活随笔為你收集整理的Java — 慎用Executors类中newFixedThreadPool()和newCachedThreadPool()的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个安全的邮件习惯如何练成的
- 下一篇: 如何解决苹果M1处理器Mac翻车问题