线程池参数到底要怎么配?
文章目錄
- 1 線程池快速回顧
- 2 現有設置參數的方法及不足
- 3 如何設置核心線程數(corePoolSize)
- 4 如何設置最大線程數(maxPoolSize)
- 5 如何改變等待隊列長度
想必大家對Java里面線程池( 類)一定不陌生吧,無論是在日常工作還是面試題里都經常會有它的身影,特別是在當前CPU動輒就是好多核的背景下,了解并使用線程池已經成為一名合格后端開發的基本功了。
相信大家也一定思考過一個問題,面對各種各樣的場景,線程池的參數到底應該怎么設計呢?這一定是一個超級難以回答的問題,幾天前的我也想不到一個標準的答案,好在是發現了美團在2020年發表過的一篇文章,里面給了一個非常高級的操作——讓線程池的參數動態化,這就極大地提高了系統的自適應能力。
https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
至于為什么我現在才看到=_=,可能因為是太懶了吧。。。好在及時發現,在此基礎上進行一些分析,不理解線程池的小伙伴們也不用擔心,我們首先來回顧一下它的核心思想,在此技術上介紹如何將參數動態化起來~
1 線程池快速回顧
《Java 并發編程的藝術》中提到了使用線程池的好處,概括起來如下:
- 降低資源損耗。通過重復利用已創建的線程降低線程創建和銷毀的損耗。
- 提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
- 提高線程的可管理性。使用線程池可以進行統一的分配,調優和監控。
Java里使用線程池,主要就是用的ThreadPoolExecutor類,先來看一下 ThreadPoolExecutor 類中的構造方法:
/*** 用給定的初始參數創建一個新的ThreadPoolExecutor。*/ public ThreadPoolExecutor(int corePoolSize,//線程池的核心線程數量int maximumPoolSize,//線程池的最大線程數long keepAliveTime,//當線程數大于核心線程數時,多余的空閑線程存活的最長時間TimeUnit unit,//時間單位BlockingQueue<Runnable> workQueue,//任務隊列,用來儲存等待執行任務的隊列ThreadFactory threadFactory,//線程工廠,用來創建線程,一般默認即可RejectedExecutionHandler handler//拒絕策略,當提交的任務過多而不能及時處理時,我們可以定制策略來處理任務) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler; }ThreadPoolExecutor 中最重要的參數:
- corePoolSize:核心線程數。最小可以同時運行的線程數。
- maximumPoolSize:當隊列中存放的任務達到隊列容量的時候,當前可以同時運行的最大線程數。
- workQueue:當新任務來的時候會先判斷當前運行的線程數量是否達到corePoolSize,如果達到的話,新任務就會被存放在隊列中。如果workQueue已經滿了的話就執行拒絕策略。
ThreadPoolExecutor 的其他參數:
- keepAliveTime:當線程池中的線程數量大于 corePoolSize 的時候,核心線程外的線程不會立即銷毀,而是會等待,直到等待的時間超過了 keepAliveTime 才會被銷毀。
- unit : keepAliveTime 參數的時間單位。
- threadFactory:executor 創建新線程的時候會用到。
- handler:拒絕策略。
當參數設置完畢后,線程池的工作原理具體是什么呢?我們可以通過下面這個面試題來理解一下:
假設我們設置的線程池參數為:corePoolSize=10, maximumPoolSize=20,queueSize = 10
20個并發任務過來,有多少個活躍線程?
10個。corePoolSize打滿,queueSize 也滿
21個并發任務過來,有多少個活躍線程?
11個。corePoolSize打滿,queueSize 也滿還多一個,maximumPoolSize = 20,所以corePoolSize + 1此時活躍的為11個。
30個并發任務過來,有多少個活躍線程?
20個。corePoolSize打滿,queueSize 也滿,corePoolSize擴充至20,此時有20個活躍任務。
31個并發任務過來,有多少個活躍線程?
20個。corePoolSize打滿,queueSize 也滿,corePoolSize擴充至20還多一個,如果是丟棄策略,此時有20個活躍任務。
上面的流程可以總結成如下所示的流程圖:(來源于https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)
2 現有設置參數的方法及不足
回顧完線程池的核心技術點之后就要開始思考本文主要討論的內容了:線程池參數應該如何設置?
如果你把這個問題輸入到瀏覽器里,極大可能是下面這種答案:
上面的理論看似很華麗,但現實卻是很殘酷的。。。你會發現雖然按照上面的指導思想進行配置了,但效果并不能讓人滿意,造成這種后果的原因有很多,包括但不僅限于:
針對上述問題,美團給出的對應的解決方案就是——線程池參數動態化
那么如何實現參數動態化呢?
接觸過微服務開發的同學們可能就會想到了,我們完全可以借助一個配置中心來做,這樣就能夠實現線程池參數的動態配置和即時生效(在阿里內部也有一個專門的中間件,diamond),省去了重新部署程序并發布的步驟,通常在企業里這一系列流程下來還是比較費時間的。
(來源于https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)
3 如何設置核心線程數(corePoolSize)
其實 ThreadPoolExecutor 類庫里直接就有這個方法:
public void setCorePoolSize(int corePoolSize) {if (corePoolSize < 0)throw new IllegalArgumentException();int delta = corePoolSize - this.corePoolSize;this.corePoolSize = corePoolSize;if (workerCountOf(ctl.get()) > corePoolSize)interruptIdleWorkers();else if (delta > 0) {// We don't really know how many new threads are "needed".// As a heuristic, prestart enough new workers (up to new// core size) to handle the current number of tasks in// queue, but stop if queue becomes empty while doing so.int k = Math.min(delta, workQueue.size());while (k-- > 0 && addWorker(null, true)) {if (workQueue.isEmpty())break;}} }
我們直接看英文注釋,這就是作者直接想要表達的意思。大致翻譯一下:
設置線程的核心數量,如果新的corePoolSize值小于當前corePoolSize值,多出來的線程將在其下次空閑時被終止。如果新的corePoolSize值大于當前corePoolSize值,就可以創建新的worker來執行隊列里的任務
(來源于https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)
4 如何設置最大線程數(maxPoolSize)
同樣地 ThreadPoolExecutor 類庫里也有這個方法:
public void setMaximumPoolSize(int maximumPoolSize) {if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)throw new IllegalArgumentException();this.maximumPoolSize = maximumPoolSize;if (workerCountOf(ctl.get()) > maximumPoolSize)interruptIdleWorkers(); }這個方法的注釋和上面的方法類似,大家可以對照著看:
邏輯也并不復雜:
JDK原生線程池ThreadPoolExecutor還提供了其他設置參數的方法:
5 如何改變等待隊列長度
等待隊列的長度capacity被final修飾符修飾,所以按理說是不能修改的
private static final int CAPACITY = (1 << COUNT_BITS) - 1;唯一可能的辦法就是自己定義一個隊列,在美團的實現里就是一個名為ResizableCapacityLinkedBlockIngQueue的隊列,根據名稱也不難看出,這個隊列的容量是可變的。
具體的實現細節美團好像并沒有公布出來,不過我們可以簡單的將原先LinkedBlockingQueue的capacity的final修飾符去掉,并提供getter和setter方法,形成我們自己的ResizableCapacityLinkedBlockIngQueue
總結
以上是生活随笔為你收集整理的线程池参数到底要怎么配?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AI理论知识整理(13)-标准基
- 下一篇: 幼儿课外活动游戏_泰国清迈大小学校介绍