并發(fā)真的比串行快嗎?

前言
在找到這個問題答案之前,我回想了自己所有掌握的知識點,以及我的相關(guān)經(jīng)驗,我發(fā)現(xiàn)我無法回答這個問題,當(dāng)然在面試中,也有類似的問題被問到過:是不是線程池越大越好?我通常的回答是,不是,要根據(jù)系統(tǒng)的內(nèi)存情況、系統(tǒng)的并發(fā)情況,綜合來看,但怎么綜合來看,我是不知道的。
直到昨天,隨手翻開《并發(fā)編程的藝術(shù)》,才讓我真正找到了這個問題的答案,當(dāng)然相關(guān)問題的答案也開始變得明朗了,所以今天我們要探討的問題,就是找到并發(fā)某些情況下慢的原因。
不知道有沒有小伙伴還不知道并發(fā)和串行,這里我們簡單說下,并發(fā)就是我們經(jīng)常說的多線程,就有由多個線程共同去完成一個任務(wù),同步進(jìn)行,目的是為了提高效率;串行也就是單線程,就是一個線程完成一個任務(wù),效率相對比較低。好了,我們開始正文吧!
一個示例
在找到問題的答案之前,我們要先寫一段測試代碼:
private?static?final?long?count?=?10000L;
public?static?void?main(String[]?args)?throws?Exception{
????concurrentcy();;
????serial();
}
private?static?void?concurrentcy()?throws?Exception?{
????long?start?=?System.currentTimeMillis();
????Thread?thread?=?new?Thread(()?->?{
????????int?a?=?0;
????????for?(long?i?=?0;?i?????????????a?+=?5;
????????}
????});
????thread.start();
????int?b?=?0;
????for?(long?i?=?0;?i?????????b--;
????}
????long?time?=?System.currentTimeMillis()?-?start;
????thread.join();
????System.out.println(String.format("concurrecy:?%dms,?b=%d",?time,?b));
}
private?static?void?serial()?{
????long?start?=?System.currentTimeMillis();
????int?a?=?0;
????for?(long?i?=?0;?i?????????a?+=?5;
????}
????int?b?=?0;
????for?(long?i?=?0;?i?????????b?--;
????}
????long?time?=?System.currentTimeMillis()?-?start;
????System.out.println(String.format("serial:?%dms,?b=%d",?time,?b));
}
上面的方法分別定義了一個多線程并發(fā)的方法和一個串行運行的方法,他們的作用都是執(zhí)行兩次for循環(huán),循環(huán)內(nèi)是簡單的業(yè)務(wù)。這里需要說明的是,thread.join()就是在此處加入線程,也就是第二次執(zhí)行。
運行結(jié)果
當(dāng)count為10000的時候,他們的運行結(jié)果如下:

還是讓人挺意外的,串行方式竟然比并行快了100倍,這個數(shù)據(jù)可能和我的電腦有關(guān)系,二代i3的老爺機(jī),多線程也太差了吧,作者的數(shù)據(jù)是差了1ms,我這也差距太大了。
count到十萬的時候,差距變小了,差了快20倍:

count到百萬的時候,差了差不多十五倍:

count到千萬的時候,差了差不多五倍:

count到一億的時候,差了不到0.5倍:

好了,這電腦該換了,多線程在各種數(shù)量級下都沒有串行效率高。直接上作者的結(jié)果吧:
| 循環(huán)次數(shù) | 串行 | 并行 | 并發(fā)比串行快多少 |
|---|---|---|---|
| 1萬 | 0 | 1 | 慢 |
| 10萬 | 4 | 3 | 差不多 |
| 100萬 | 5 | 5 | 差不多 |
| 1000萬 | 18 | 9 | 快一倍 |
| 1億 | 130 | 77 | 快一倍 |
結(jié)論
根據(jù)上面這些結(jié)果來看,在數(shù)據(jù)量較小的情況下,并發(fā)效率不如串行,但是隨著數(shù)據(jù)量不斷增大,并發(fā)的效率就體現(xiàn)出來了。
數(shù)據(jù)量小的時候,并行慢的原因是上下文切換比較耗時。按照我們的代碼,所有業(yè)務(wù)執(zhí)行完成需要切換4次:第一次,主線程切換到第一次循環(huán)線程,第一次循環(huán)線程切換到主線程,主線程切換到第二次循環(huán)線程,第二次循環(huán)線程切換到主線程。所以數(shù)據(jù)量小的時候,大部分時間都花費在線程之間的上下文切換上了,所以比較慢,后面隨著數(shù)據(jù)量增加,這種上下文切換時長相比程序執(zhí)行時長就可以忽略了,所以這時候就是它發(fā)揮真正的技術(shù)的時候了。
并發(fā)慢的原因在于上下文切換,所以在使用多線程的時候,我們要盡可能減少線程之間的上下文切換,最明顯的一個點就是,在使用線程池的時候,不要把線程池設(shè)置過大,過大會導(dǎo)致上下文切換過于頻繁,從而讓程序效率變低。這里提供幾個命令(書里面的知識),可以讓你查看系統(tǒng)的上下文切換數(shù)據(jù):
vmstat?#?統(tǒng)計上下文切換次數(shù)
lmbench3?#?統(tǒng)計上下文切換時長
這兩個工具都是linux環(huán)境的,可以協(xié)助你排查線程池的問題。
從程序?qū)用?,可以通過如下方式,減少上下文切換:
無鎖并發(fā)編程 CAS算法 使用最少線程 協(xié)程
好了,今天就到這里吧
- END -