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