10|Java線程(中):創(chuàng)建多少線程才是合適的?

在 Java 領(lǐng)域,實(shí)現(xiàn)并發(fā)程序的主要手段就是多線程,使用多線程還是比較簡(jiǎn)單的,但是使用多個(gè)線程卻是個(gè)困難的問(wèn)題,工作中,經(jīng)常有人問(wèn),“各種線程池的線程數(shù)量調(diào)整成多少是合適的?”或者“Tomcat 的線程數(shù)、Jdbc 連接池的連接數(shù)是多少?”等等。那我們應(yīng)該如何設(shè)置合適的線程數(shù)呢?

要解決這個(gè)問(wèn)題,首先要分析以下兩個(gè)問(wèn)題:

1、為什么要使用多線程

2、多線程的應(yīng)用場(chǎng)景有哪些

為什么要使用多線程?

使用多線程,本質(zhì)上就是提升程序性能。不過(guò)此刻談到性能,可能在你腦海中比較籠統(tǒng),基本上就是 快、快、快,這種無(wú)法度量的感性認(rèn)識(shí)很不科學(xué)。所以在提升性能之前 的首要問(wèn)題是:如何度量性能。

度量性能的指標(biāo)有很多,但是有兩個(gè)指標(biāo)是最核心的,他們就是延遲量和吞吐量。延遲指的是發(fā)出請(qǐng)求到收到響應(yīng)的時(shí)間;延遲越短,意味著程序執(zhí)行的越快,性能也就越好。吞吐量指的是單位時(shí)間內(nèi)能處理請(qǐng)求的數(shù)量;吞吐量越大,意味著程序能處理的請(qǐng)求越多,性能也就越好。這兩個(gè)指標(biāo)內(nèi)有一定的聯(lián)系(同等條件下,延遲越短,吞吐量越大),但是由于他們隸屬于不同的維度(一個(gè)時(shí)間維度,一個(gè)空間維度),并不能相互轉(zhuǎn)換。

我們所謂提升性能,從度量的角度,主要是降低延遲,提高吞吐量。這也是我們使用多線程的主要目的。那我們?cè)撛趺唇档脱舆t,提高吞吐量呢?這個(gè)就要從多線程的應(yīng)用場(chǎng)景說(shuō)起了。

多線程的應(yīng)用場(chǎng)景

要想“降低延遲,提高吞吐量”,對(duì)應(yīng)的方法呢,基本上有兩個(gè)方向, 一個(gè)方向是優(yōu)化算法,兩一個(gè)方向是將硬件的性能發(fā)揮到極致。前者屬于算法范疇,后者則是和并發(fā)編程息息相關(guān)了。那計(jì)算機(jī)主要有哪些硬件了呢?主要是兩類(lèi):一個(gè)是I/O,一個(gè)是 CPU。 簡(jiǎn)言之,在并發(fā)編程領(lǐng)域,提升性能本質(zhì)上就是提升硬件的利用率,再具體來(lái)說(shuō),就是提升I/O 的利用率和 CPU 的利用率

估計(jì)這個(gè)詞你會(huì)有疑問(wèn),操作系統(tǒng)不是已經(jīng)解決了硬件的利用率問(wèn)題了嘛?的卻是這樣,例如操作系統(tǒng)已經(jīng)解決了磁盤(pán)和網(wǎng)卡利用率問(wèn)題,利用中斷機(jī)制還能避免CPU 輪詢(xún),I/O 狀態(tài),也提升了 CPU 的利用率。 但是操作系統(tǒng)解決硬件利用率問(wèn)題的對(duì)象往往是單一的硬件設(shè)備,而我們的并發(fā)程序,往往需要CPU 和 I/O 設(shè)備相互配合工作, 也就是說(shuō),我們需要解決CPU 和 I/O 設(shè)備綜合利用率的問(wèn)題。

關(guān)于這個(gè)綜合利用率,操作系統(tǒng)雖然沒(méi)有辦法完美解決,但是卻給我們提供了解決方案。:多線程

下面我們通過(guò)一個(gè)簡(jiǎn)單的示例:如何利用多線程來(lái)提升 CPU 和 I/O 設(shè)備的利用率? 假設(shè)程序按照 CPU 計(jì)算和 I/O 操作交叉執(zhí)行的方式運(yùn)行,而且 CPU 計(jì)算和 I/O 操作的耗時(shí)是 1:1。

如下圖所示,我們只有一個(gè)線程,執(zhí)行CPU 計(jì)算的時(shí)候, I/O 設(shè)備空閑; 執(zhí)行 I/O 操作的時(shí)候, CPU 空閑, 所以 CPU 的利用率和 I/O 設(shè)備的利用率都是 50%。

img

如果有兩個(gè)線程,如下圖所示,當(dāng)線程A 執(zhí)行 CPU 計(jì)算的時(shí)候, 線程 B 執(zhí)行 I/O 操作; 當(dāng)線程 A 執(zhí)行 I/O 操作的時(shí)候,線程 B 執(zhí)行 CPU 計(jì)算,這樣 CPU 的利用率和 I/O 設(shè)備的利用率就都達(dá)到了 100%。

img

我們將 CPU 的利用率和 I/O 設(shè)備的利用率都提升到了 100%, 會(huì)對(duì)性能產(chǎn)生了哪些影響呢?通過(guò)上面圖示,很容易看出:?jiǎn)挝粫r(shí)間處理的請(qǐng)求數(shù)量翻了一番,也就是說(shuō)吞吐量提高了一倍。此時(shí)可以你想思維以下,**如果 CPU 和 I/O 設(shè)備的利用率都很低,那么可以嘗試通過(guò)增加線程來(lái)提高吞吐量。 **

在單核時(shí)代,多線程主要用來(lái)平衡CPU 和 I/O 設(shè)備的。 如果程序只有CPU 計(jì)算, 而沒(méi)有I/O 操作的話, 多線程不但不會(huì)提升性能,還會(huì)使性能變得更差,原因是增加了線程切換成本。但是在多核時(shí)代,這種純計(jì)算型的程序可以利用多線程來(lái)提高性能。為什么呢?因?yàn)槔枚嗪丝梢越档晚憫?yīng)時(shí)間。

為了便于你理解,這里我舉個(gè)簡(jiǎn)單的例子說(shuō)明一下:計(jì)算 1+2+… … +100 億的值,如果在 4 核的 CPU 上利用 4 個(gè)線程執(zhí)行,線程 A 計(jì)算 [1,25 億),線程 B 計(jì)算 [25 億,50 億),線程 C 計(jì)算 [50,75 億),線程 D 計(jì)算 [75 億,100 億],之后匯總,那么理論上應(yīng)該比一個(gè)線程計(jì)算 [1,100 億] 快將近 4 倍,響應(yīng)時(shí)間能夠降到 25%。一個(gè)線程,對(duì)于 4 核的 CPU,CPU 的利用率只有 25%,而 4 個(gè)線程,則能夠?qū)?CPU 的利用率提高到 100%。

img

創(chuàng)建多少線程合適?

創(chuàng)建多少線程合適,需要看多線程具體應(yīng)用場(chǎng)景。我們的程序一般是CPU 計(jì)算和 I/O 操作交叉執(zhí)行的,由于 I/O 設(shè)備的速度相對(duì)于 CPU 來(lái)說(shuō)都很慢,所以大部分情況下,I/O 操作執(zhí)行的時(shí)間相對(duì)于 CPU 計(jì)算來(lái)說(shuō)都非常長(zhǎng),這種場(chǎng)景我們一般都稱(chēng)為 I/O 密集型計(jì)算;

和 I/O 密集型計(jì)算相對(duì)的就是 CPU 密集型計(jì)算了,CPU 密集型計(jì)算大部分場(chǎng)景下都是純 CPU 計(jì)算。I/O 密集型程序和 CPU 密集型程序,計(jì)算最佳線程數(shù)的方法是不同的。

下面我們對(duì)這兩個(gè)場(chǎng)景分別說(shuō)明。

對(duì)于 CPU 密集型計(jì)算,多線程本質(zhì)上是提升多核 CPU 的利用率, 所以對(duì)于一個(gè)4 核的 CPU,每個(gè)核一個(gè)線程,理論上創(chuàng)建 4 個(gè)線程就可以了, 再多創(chuàng)建線程只是增加線程切換的成本。所以,對(duì)于 CPU 密集型的計(jì)算場(chǎng)景,理論上“線程的數(shù)量 =CPU 核數(shù)”就是最合適的。不過(guò)在工程上,線程的數(shù)量一般會(huì)設(shè)置為“CPU 核數(shù) +1”, 這樣的話,當(dāng)線程因?yàn)榕紶柕膬?nèi)存頁(yè)失效,或者其他原因?qū)е伦枞?,這個(gè)額外的線程可以頂上,從而保證CPU的利用率。

對(duì)于I/O 密集型的計(jì)算場(chǎng)景,比如前面我們提到的例子中,如果 CPU 計(jì)算和 I/O 操作的耗時(shí)是 1:1,那么 2 個(gè)線程是最合適的。 如果 CPU 計(jì)算和 I/O 操作的耗時(shí)是 1:2,那多少個(gè)線程合適呢?是 3 個(gè)線程,如下圖所示:CPU 在 A、B、C 三個(gè)線程之間切換,對(duì)于線程 A,當(dāng) CPU 從 B、C 切換回來(lái)時(shí),線程 A 正好執(zhí)行完 I/O 操作。這樣 CPU 和 I/O 設(shè)備的利用率都達(dá)到了 100%。

img

通過(guò)上面的例子,我們就會(huì)發(fā)現(xiàn),對(duì)于I/O 密集型計(jì)算場(chǎng)景, 最佳的線程數(shù)是與程序中CPU 計(jì)算和 I/O 操作的耗時(shí)比相關(guān)的,我們可以總結(jié)出這樣一個(gè)公式:

最佳線程數(shù) =1 +(I/O 耗時(shí) / CPU 耗時(shí))

我們令 R=I/O 耗時(shí) / CPU 耗時(shí),綜合上圖,可以這樣理解:當(dāng)線程 A 執(zhí)行 IO 操作時(shí),另外 R 個(gè)線程正好執(zhí)行完各自的 CPU 計(jì)算。這樣 CPU 的利用率就達(dá)到了 100%。

不過(guò)上面這個(gè)公式,是針對(duì)單核CPU 的,至于多核CPU ,也很簡(jiǎn)單,只要等比擴(kuò)大就可以了,計(jì)算公式如下:

最佳線程數(shù) =CPU 核數(shù) * [ 1 +(I/O 耗時(shí) / CPU 耗時(shí))]

總結(jié)

很多人都知道線程不是越多越好,但是設(shè)置多少合適,卻又拿不定注意,其實(shí)只要把我一條原則就可以了,這條原則就是 將硬件性能發(fā)揮到極致。上面我們針對(duì)CPU密集型和I/O 密集型計(jì)算場(chǎng)景都給出了理論上的最佳公式,這些公式背后的目標(biāo)其實(shí)就是將硬件的性能發(fā)揮到極致。

對(duì)于 I/O 密集型計(jì)算場(chǎng)景,I/O 耗時(shí)和 CPU 耗時(shí)的比值是一個(gè)關(guān)鍵參數(shù),不幸的是這個(gè)參數(shù)是未知的,而且是動(dòng)態(tài)變化的,所以工程上,我們要估算這個(gè)參數(shù),然后做各種不同場(chǎng)景下的壓測(cè)來(lái)驗(yàn)證我們的估計(jì)。不過(guò)工程上,原則還是將硬件的性能發(fā)揮到極致,所以壓測(cè)時(shí),我們需要重點(diǎn)關(guān)注 CPU、I/O 設(shè)備的利用率和性能指標(biāo)(響應(yīng)時(shí)間、吞吐量)之間的關(guān)系。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容