《并發(fā)與高并發(fā)系列第一集-基礎(chǔ)與概念》
作者丨安琪拉
來源丨安琪拉的博客
面試官:看你簡歷上寫,最近正在寫并發(fā)編程方面的博客,是吧?
安琪拉:閑來無事,看看閑書,寫寫段子,承蒙讀者厚愛,有此打算。
面試官:少跟我這拽文,“閑來無事”?阿里不用996嗎?
安琪拉:修福報(bào),你知道嗎?..... 技術(shù)人的日常,能算996嗎?
面試官:算了算了,還是聊正題,你先跟我講講什么是并發(fā)?
安琪拉:并發(fā)就是存在兩個(gè)或多個(gè)線程,這些線程同時(shí)操作相同的物理機(jī)中的資源。
面試官:那并發(fā)跟并行有什么區(qū)別呢?
安琪拉:舉個(gè)生活中的例子就懂了:
你在打王者榮耀,這個(gè)時(shí)候女朋友找你視頻,你一直打完王者榮耀才接,說明你不支持并發(fā)(也不支持并行); 你在打王者榮耀,這個(gè)時(shí)候女朋友給你發(fā)了微信,你退出王者榮耀,回完微信再回到王者,微信和王者間來回切換,說明你支持并發(fā),但不支持并行; 你在打王者榮耀,這個(gè)時(shí)候女朋友給你打電話,你邊打榮耀邊接電話,說明你支持并行。
并行的關(guān)鍵點(diǎn)是物理的“同時(shí)”,我們在單核CPU的時(shí)候,既能寫代碼也能聽歌,這個(gè)多線程實(shí)際是基于操作系統(tǒng)根據(jù)CPU時(shí)間片做的任務(wù)輪轉(zhuǎn),是偽“同時(shí)”,只能說是并發(fā),不能算并行,但是多核CPU可以支持每個(gè)核同時(shí)運(yùn)行任務(wù),是真實(shí)的“同時(shí)”,是并行。
Erlang 之父 Joe Armstrong 畫了一張圖解釋了并發(fā)與并行的區(qū)別,Concurrent (并發(fā)),Parallel (并行)。

并發(fā)允許二隊(duì)小孩輪流使用咖啡機(jī),并行是同時(shí)存在二臺咖啡機(jī),二隊(duì)小孩同時(shí)使用,不沖突。
面試官:那高并發(fā)呢?你了解高并發(fā)嗎?
安琪拉:【心里想,該來的還是來了,要造火箭了】
你說High Concurrency(高并發(fā))是吧(先拽句英文)。
通常我們談?wù)摬l(fā)的時(shí)候,更多的關(guān)注點(diǎn)在于線程安全,但是討論高并發(fā)時(shí),關(guān)注點(diǎn)不僅僅是線程安全問題,而是如何在短時(shí)間內(nèi)處理大量請求,保證系統(tǒng)響應(yīng)時(shí)間和吞吐量的可靠,更多關(guān)注的是穩(wěn)定性問題(SRE),高并發(fā)涉及的是完整的系統(tǒng)知識,線程安全只是其中一小部分。
高并發(fā)是現(xiàn)在互聯(lián)網(wǎng)設(shè)計(jì)系統(tǒng)中需要考慮的一個(gè)重要因素之一,通常來說,就是通過嚴(yán)謹(jǐn)?shù)脑O(shè)計(jì)來保證系統(tǒng)能夠同時(shí)并行處理很多的請求。這就是大家常說的「 高并發(fā) 」。也就是說系統(tǒng)能夠在某一時(shí)間段內(nèi)提供很多請求,但是不會(huì)影響系統(tǒng)的性能。如果想設(shè)計(jì)出高可用和高性能的系統(tǒng),就應(yīng)該從很多的方面來考慮,例如應(yīng)該從硬件、軟件、編程語言的選擇、網(wǎng)絡(luò)方面的考慮、系統(tǒng)的整體架構(gòu)、數(shù)據(jù)結(jié)構(gòu)、算法的優(yōu)化、數(shù)據(jù)庫的優(yōu)化等等多方面。這其中的每一點(diǎn)展開來說都要說很多的知識,安琪拉會(huì)在后續(xù)課程更新這部分內(nèi)容。
面試官:那你跟我講講你們系統(tǒng)的QPS有多少?
安琪拉:大促場景能有個(gè)10W+的QPS,日常業(yè)務(wù)高峰期也有2W+,其他時(shí)間幾千。
其實(shí)對于大部分的系統(tǒng),幾十、幾百很正常,QPS能過千的就已經(jīng)不低了,有的業(yè)務(wù)會(huì)有峰值,QPS穩(wěn)定過萬的系統(tǒng)實(shí)際中不多,所以大家日常可以關(guān)注一下自己系統(tǒng)的QPS,這個(gè)問題面試經(jīng)常會(huì)問。
面試官:一般我們有什么工具可以模擬并發(fā)請求呢?
安琪拉:PostMan、Apache Bench(AB)、Jmeter,推薦使用Jmeter。
面試官:那你能寫段代碼,演示一下并發(fā)安全的問題嗎?
安琪拉:可以啊。筆遞給我一下,順便幫我拿下A4紙。
public class ConcurrencySafeTest {
private static int counter = 0;
public static void main(String[] args) {
//使用線程池
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
//提交2000個(gè)任務(wù)
for(int i = 0; i < 2000; i++) {
threadPool.submit(new Add());
}
threadPool.shutdown();
System.out.println(counter);
}
static class Add implements Runnable {
@Override
public void run() {
counter++;
}
}
}
我們進(jìn)行計(jì)數(shù)操作,執(zhí)行2000次,預(yù)期的執(zhí)行結(jié)果應(yīng)該是2000,但是實(shí)際執(zhí)行結(jié)果如下:
1971
1987
因?yàn)椤恫l(fā)》系列是從基礎(chǔ)開始講的,上面的代碼部分內(nèi)容涉及到后面的一些內(nèi)容,比如線程池和線程的使用,這里只要大致了解并發(fā)的安全問題,后面會(huì)有詳細(xì)說明,后面面試官的問題作為擴(kuò)展閱讀。
面試官:看到你代碼中用了CachedThreadPool,那2000次任務(wù)執(zhí)行,CachedThreadPool 線程池創(chuàng)建了多少個(gè)線程?
安琪拉:答案是不確定,CachedThreadPool 緩存了線程(復(fù)用線程),沒有讓任務(wù)排隊(duì),來一個(gè)任務(wù),要么復(fù)用已有線程處理,要么新建一個(gè)線程處理。那我們怎么確定線程池創(chuàng)建過多少個(gè)線程呢?可以加一段代碼打印出來。
如下:
private static int counter = 0;
public static void main(String[] args) {
//使用線程池
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
//提交2000個(gè)任務(wù)
for(int i = 0; i < 2000; i++) {
threadPool.submit(new Add());
}
threadPool.shutdown();
//打印最多使用線程數(shù)
System.out.println("largestPoolSize:" + threadPool.getLargestPoolSize());
System.out.println(counter);
}
輸出結(jié)果如下:
第一次:
largestPoolSize:11
1937
第一次:
largestPoolSize:14
1956
第一次:
largestPoolSize:31
1970
可以看到每次都不一樣,線程池之前有文章講過,這個(gè)系列后面還會(huì)深入講解。
關(guān)于 largestPoolSize, 注釋說明了,記錄線程池中最大的線程數(shù)。
/**
* Tracks largest attained pool size. Accessed only under
* mainLock.
*/
private int largestPoolSize;
面試官:看你代碼中寫了調(diào)用線程池的shutdown,那shutdown 和 shutdownNow 方法什么區(qū)別?
安琪拉:shutdown 是將線程池的狀態(tài)設(shè)置為SHUTWDOWN狀態(tài),正在執(zhí)行的任務(wù)會(huì)繼續(xù)執(zhí)行下去,沒有被執(zhí)行的則中斷。而shutdownNow則是將線程池的狀態(tài)設(shè)置為STOP,正在執(zhí)行的任務(wù)則被停止,沒被執(zhí)行任務(wù)的則返回。
源碼對比:
//shutdown
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//設(shè)置線程池狀態(tài)為SHUTDOWN
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//設(shè)置線程池狀態(tài)為STOP
advanceRunState(STOP);
interruptWorkers();
//把隊(duì)列剩余等待執(zhí)行任務(wù)取出,返回
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
面試官:線程池有哪幾種狀態(tài)?
安琪拉:5種,注意這里說的是線程池的狀態(tài),不是線程的狀態(tài)。下面是線程池的狀態(tài)流轉(zhuǎn)圖:

本文是《并發(fā)》系列第一集,主要介紹了一些并發(fā)、并行、高并發(fā)的一些基礎(chǔ)概念,以及并發(fā)安全問題的案例,下一集講并發(fā)的風(fēng)險(xiǎn)與優(yōu)勢和CPU多級緩存,以及一些內(nèi)存操作的指令,然后說Java內(nèi)存模型。
完整大綱參考:

-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取