并發(fā)編程的三大核心問題
并發(fā)編程并不是一項(xiàng)孤立存在的技術(shù),也不是脫離現(xiàn)實(shí)生活場(chǎng)景而提出的一項(xiàng)技術(shù)。
相反,并發(fā)編程是一項(xiàng)綜合性的技術(shù),同時(shí),它與現(xiàn)實(shí)生活中 的場(chǎng)景有著緊密的聯(lián)系。
并發(fā)編程有三大核心問題:
- 分工問題
- 同步問題
- 互斥問題
本文就對(duì)這三大核心問題進(jìn)行簡單的介紹。
1 分工問題
關(guān)于分工,比較官方的解釋是:一個(gè)比較大的任務(wù)被拆分成多個(gè)大小合適的任務(wù),這些大小合適的任務(wù)被交給合適的線程去執(zhí)行。
分工強(qiáng)調(diào)的是執(zhí)行的性能。
▊ 類比現(xiàn)實(shí)案例
可以類比現(xiàn)實(shí)生活中的場(chǎng)景來理解分工,例如,如果你是一家上市公司的 CEO,那么,你的主要工作就是規(guī)劃公司的戰(zhàn)略方向和管理好公司。就如何管理好公司而言,涉及的任務(wù)就比較多了。
這里,可以將管理好公司看作一個(gè)很大的任務(wù),這個(gè)很大的任務(wù)可以包括人員招聘與管理、 產(chǎn)品設(shè)計(jì)、產(chǎn)品開發(fā)、產(chǎn)品運(yùn)營、產(chǎn)品推廣、稅務(wù)統(tǒng)計(jì)和計(jì)算等。如果將這些工作任務(wù)都交給 CEO一個(gè)人去做,那么估計(jì) CEO 會(huì)被累趴下的。CEO一人做完公司所有日常工作如圖1所示。
圖1 CEO 一人做完公司所有日常工作
如圖1 所示,公司 CEO 一個(gè)人做完公司所有日常工作是一種非常不可取的方式,這將導(dǎo)致公司無法正常經(jīng)營,那么應(yīng)該如何做呢?
有一種很好的方式是分解公司的日常工作,將人員招聘與管理工作交給人力資源部,將產(chǎn) 品設(shè)計(jì)工作交給設(shè)計(jì)部,將產(chǎn)品開發(fā)工作交給研發(fā)部,將產(chǎn)品運(yùn)營和產(chǎn)品推廣工作分別交給運(yùn) 營部和市場(chǎng)部,將公司的稅務(wù)統(tǒng)計(jì)和計(jì)算工作交給財(cái)務(wù)部。
這樣,CEO 的重點(diǎn)工作就變成了及時(shí)了解各部門的工作情況,統(tǒng)籌并協(xié)調(diào)各部門的工作, 并思考如何規(guī)劃公司的戰(zhàn)略。
公司分工后的日常工作如圖2所示。
圖2 公司分工后的日常工作
將公司的日常工作分工后,可以發(fā)現(xiàn),各部門之間的工作是可以并行推進(jìn)的。例如,在人力資源部進(jìn)行員工的績效考核時(shí),設(shè)計(jì)部和研發(fā)部正在設(shè)計(jì)和開發(fā)公司的產(chǎn)品,與此同時(shí),公司的運(yùn)營人員正在和設(shè)計(jì)人員與研發(fā)人員溝通如何更好地完善公司的產(chǎn)品,而市場(chǎng)部正在加大力度宣傳和推廣公司的產(chǎn)品,財(cái)務(wù)部正在統(tǒng)計(jì)和計(jì)算公司的各種財(cái)務(wù)報(bào)表等。一切都是那么有條不紊。
所以,在現(xiàn)實(shí)生活中,安排合適的人去做合適的事情是非常重要的。映射到并發(fā)編程領(lǐng)域 也是同樣的道理。
▊ 并發(fā)編程中的分工
在并發(fā)編程中,同樣需要將一個(gè)大的任務(wù)拆分成若干比較小的任務(wù),并將這些小任務(wù)交給 不同的線程去執(zhí)行,如圖3所示。
圖3 將一個(gè)大的任務(wù)拆分成若干比較小的任務(wù)
在并發(fā)編程中,由于多個(gè)線程可以并發(fā)執(zhí)行,所以在一定程度上能夠提高任務(wù)的執(zhí)行效率。
在并發(fā)編程領(lǐng)域,還需要注意一個(gè)問題就是:將任務(wù)分給合適的線程去做。也就是說,該由主線程執(zhí)行的任務(wù)不要交給子線程去做,否則,是解決不了問題的。
這就好比一家公司的 CEO 將規(guī)劃公司未來的工作交給一位產(chǎn)品開發(fā)人員一樣,不僅不能規(guī)劃好公司的未來,甚至?xí)c公司的價(jià)值觀背道而馳。
在 Java 中,線程池、Fork/Join 框架和 Future 接口都是實(shí)現(xiàn)分工的方式。在多線程設(shè)計(jì)模式中,Guarded Suspension 模式、Thread-Per-Message 模式、生產(chǎn)者—消費(fèi)者模式、兩階段終止模式、Worker-Thread 模式和 Balking 模式都是分工問題的實(shí)現(xiàn)方式。
2 同步問題
在并發(fā)編程中,同步指一個(gè)線程執(zhí)行完自己的任務(wù)后,以何種方式來通知其他的線程繼續(xù)執(zhí)行任務(wù),也可以將其理解為線程之間的協(xié)作,同步強(qiáng)調(diào)的是執(zhí)行的性能。
▊ 類比現(xiàn)實(shí)案例
可以在現(xiàn)實(shí)生活中找到與并發(fā)編程中的同步問題相似的案例。
例如,張三、李四和王五共同開發(fā)一個(gè)項(xiàng)目,張三是一名前端開發(fā)人員,他需要等待李四的開發(fā)接口任務(wù)完成再開始渲染 頁面,而李四又需要等待王五的服務(wù)開發(fā)工作完成再寫接口。
也就是說,任務(wù)之間是存在依賴關(guān)系的,前面的任務(wù)完成后,才能執(zhí)行后面的任務(wù)。
在現(xiàn)實(shí)生活中,這種任務(wù)的同步,更多的是靠人與人之間的交流和溝通來實(shí)現(xiàn)的。例如,王五的服務(wù)開發(fā)任務(wù)完成了,告訴李四,李四馬上開始執(zhí)行開發(fā)接口任務(wù)。等李四的接口開發(fā)完成后,再告訴張三,張三馬上調(diào)用李四開發(fā)的接口將返回的數(shù)據(jù)渲染到頁面上。現(xiàn)實(shí)生活中 的同步模型如圖4所示。
圖4 現(xiàn)實(shí)生活中的同步模型
由圖4可以看出,在現(xiàn)實(shí)生活中,張三、李四和王五的任務(wù)之間是有依賴關(guān)系的,張三渲染頁面的任務(wù)依賴?yán)钏拈_發(fā)接口的任務(wù)完成,李四開發(fā)接口的任務(wù)依賴王五開發(fā)服務(wù)的任務(wù)完成。
▊ 并發(fā)編程中的同步
在并發(fā)編程領(lǐng)域,同步機(jī)制指一個(gè)線程的任務(wù)執(zhí)行完成后,通知其他線程繼續(xù)執(zhí)行任務(wù)的方式,并發(fā)編程同步簡易模型如圖5所示。
圖5 并發(fā)編程同步簡易模型
由圖5可以看出,在并發(fā)編程中,多個(gè)線程之間的任務(wù)是有依賴關(guān)系的。
線程 A 需要阻塞等待線程 B 執(zhí)行完任務(wù)才能開始執(zhí)行任務(wù),線程 B 需要阻塞等待線程 C 執(zhí)行完任務(wù)才能開始執(zhí)行任務(wù)。線程 C 執(zhí)行完任務(wù)會(huì)喚醒線程 B 繼續(xù)執(zhí)行任務(wù),線程 B 執(zhí)行完任務(wù)會(huì)喚醒線程 A 繼續(xù)執(zhí)行任務(wù)。
這種線程之間的同步機(jī)制,可以使用如下的 if 偽代碼來表示。
if(依賴的任務(wù)完成){
執(zhí)行當(dāng)前任務(wù)
}else{
繼續(xù)等待依賴任務(wù)的執(zhí)行
}
上述 if 偽代碼所代表的含義是:當(dāng)依賴的任務(wù)完成時(shí),執(zhí)行當(dāng)前任務(wù),否則,繼續(xù)等待依 賴任務(wù)的執(zhí)行。
在實(shí)際場(chǎng)景中,往往需要及時(shí)判斷出依賴的任務(wù)是否已經(jīng)完成,這時(shí)就可以使用 while 循 環(huán)來代替 if 判斷, while 偽代碼如下。
while(依賴的任務(wù)未完成){
繼續(xù)等待依賴任務(wù)的執(zhí)行
}
執(zhí)行當(dāng)前任務(wù)
上述 while 偽代碼所代表的含義是:如果依賴的任務(wù)未完成,則一直等待,直到依賴的任務(wù)完成,才執(zhí)行當(dāng)前任務(wù)。
在并發(fā)編程領(lǐng)域,同步機(jī)制有一個(gè)非常經(jīng)典的模型——生產(chǎn)者—消費(fèi)者模型。如果隊(duì)列已滿,則生產(chǎn)者線程需要等待,如果隊(duì)列不滿,則需要喚醒生產(chǎn)者線程;如果隊(duì)列為空,則消費(fèi)者線程需要等待,如果隊(duì)列不為空,則需要喚醒消費(fèi)者。
可以使用下面的偽代碼來表示生產(chǎn)者—消費(fèi)者模型。
- 生產(chǎn)者偽代碼
while(隊(duì)列已滿){
生產(chǎn)者線程等待
}
喚醒生產(chǎn)者
- ?消費(fèi)者偽代碼
while(隊(duì)列為空){
消費(fèi)者等待
}
喚醒消費(fèi)者
在Java 中,Semaphore、Lock、synchronized.、CountDownLatch、CyclicBarrier、Exchanger 和 Phaser 等工具類或框架實(shí)現(xiàn)了同步機(jī)制。
3 互斥問題
在并發(fā)編程中,互斥問題一般指在同一時(shí)刻只允許一個(gè)線程訪問臨界區(qū)的共享資源。互斥強(qiáng)調(diào)的是多個(gè)線程執(zhí)行任務(wù)時(shí)的正確性。
▊ 類比現(xiàn)實(shí)案例
互斥問題在現(xiàn)實(shí)中的一個(gè)典型場(chǎng)景就是交叉路口的多輛車匯入一個(gè)單行道,如圖6所示。
圖6 交叉路口的多輛車匯入一個(gè)單行道
從圖6 可以看出,當(dāng)多輛車經(jīng)過交叉路口匯入同一個(gè)單行道時(shí),由于單行道的入口只能容納一輛車通過,所以其他的車輛需要等待前面的車輛通過單行道入口后,再依次有序通過單行道入口。這就是現(xiàn)實(shí)生活中的互斥場(chǎng)景。
▊ 并發(fā)編程中的互斥
在并發(fā)編程中,分工和同步強(qiáng)調(diào)的是任務(wù)的執(zhí)行性能,而互斥強(qiáng)調(diào)的則是執(zhí)行任務(wù)的正確性,也就是線程的安全問題。
如果在并發(fā)編程中,多個(gè)線程同時(shí)進(jìn)入臨界區(qū)訪問同一個(gè)共享變量,則可能產(chǎn)生線程安全問題,這是由線程的原子性、可見性和有序性問題導(dǎo)致的。
而在并發(fā)編程中解決原子性、可見性和有序性問題的核心方案就是線程之間的互斥。
例如,可以使用JVM中提供的synchronized鎖來實(shí)現(xiàn)多個(gè)線程之間的互斥,使用synchronized鎖的偽代碼如下。
- 修飾方法
public synchronized void methodName(){
//省略具體方法
}
- 修飾代碼塊
public void methodName(){
synchronized(this){
//省略具體方法
}
}
public void methodName(){
synchronized(obj){
//省略具體方法
}
}
public void methodName(){
synchronized(ClassName.class){
//省略具體方法
}
}
- 修飾靜態(tài)方法
public synchronized static void staticMethodName(){
//省略具體方法
}
除了 synchronized 鎖,Java 還提供了 ThreadLocal、CAS、原子類和以CopyOnWrite 開頭的并發(fā)容器類、Lock 鎖及讀/寫鎖等,它們都實(shí)現(xiàn)了線程的互斥機(jī)制。