Java 實現 100 萬 - 併發,搞懂這些,騷操作-

Java 實現百萬級併發,需要注意的,三大核心問題,你知道嗎?併發編程並不是一項孤立存在的技術,也不是脫離現實生活場景而提出的一項技術。

相反,實現百萬級併發編是一項綜合性的技術,同時,它與現實生活中 的場景有着緊密的聯繫。

搞懂併發編程有三大核心問題

本文就對這三大核心問題進行簡單的介紹

1、分工問題

**關於分工,比較官方的解釋是:**一個比較大的任務被拆分成多個大小合適的任務,這些大小合適的任務被交給合適的線程去執行。

分工強調的是執行的性能。

類比現實案例

可以類比現實生活中的場景來理解分工,例如,如果你是一家上市公司的 CEO,那麼,你的主要工作就是規劃公司的戰略方向和管理好公司。就如何管理好公司而言,涉及的任務就比較多了。

這裏,可以將管理好公司看作一個很大的任務,這個很大的任務可以包括人員招聘與管理、 產品設計、產品開發、產品運營、產品推廣、稅務統計和計算等。如果將這些工作任務都交給 CEO 一個人去做,那麼估計 CEO 會被累趴下的。CEO 一人做完公司所有日常工作如圖 1 所示。

 

圖 1CEO 一人做完公司所有日常工作

如圖 1 所示,公司 CEO 一個人做完公司所有日常工作是一種非常不可取的方式,這將導致公司無法正常經營,那麼應該如何做呢?

有一種很好的方式是分解公司的日常工作,將人員招聘與管理工作交給人力資源部,將產 品設計工作交給設計部,將產品開發工作交給研發部,將產品運營和產品推廣工作分別交給運 營部和市場部,將公司的稅務統計和計算工作交給財務部。

這樣,CEO 的重點工作就變成了及時瞭解各部門的工作情況,統籌並協調各部門的工作, 並思考如何規劃公司的戰略。

公司分工後的日常工作如圖 2 所示。

 

圖 2 公司分工後的日常工作

將公司的日常工作分工後,可以發現,各部門之間的工作是可以並行推進的。例如,在人力資源部進行員工的績效考覈時,設計部和研發部正在設計和開發公司的產品,與此同時,公司的運營人員正在和設計人員與研發人員溝通如何更好地完善公司的產品,而市場部正在加大力度宣傳和推廣公司的產品,財務部正在統計和計算公司的各種財務報表等。一切都是那麼有條不紊。

所以,在現實生活中,安排合適的人去做合適的事情是非常重要的。映射到併發編程領域 也是同樣的道理。

併發編程中的分工

在併發編程中,同樣需要將一個大的任務拆分成若干比較小的任務,並將這些小任務交給 不同的線程去執行,如圖 3 所示。

 

圖 3 將一個大的任務拆分成若干比較小的任務

在併發編程中,由於多個線程可以併發執行,所以在一定程度上能夠提高任務的執行效率。

**在併發編程領域,還需要注意一個問題就是:**將任務分給合適的線程去做。也就是說,該由主線程執行的任務不要交給子線程去做,否則,是解決不了問題的。

這就好比一家公司的 CEO 將規劃公司未來的工作交給一位產品開發人員一樣,不僅不能規劃好公司的未來,甚至會與公司的價值觀背道而馳。

在 Java 中,線程池、Fork/Join 框架和 Future 接口都是實現分工的方式。在多線程設計模式中,Guarded Suspension 模式、Thread-Per-Message 模式、生產者—消費者模式、兩階段終止模式、Worker-Thread 模式和 Balking 模式都是分工問題的實現方式。

2、同步問題

在併發編程中,同步指一個線程執行完自己的任務後,以何種方式來通知其他的線程繼續執行任務,也可以將其理解爲線程之間的協作,同步強調的是執行的性能。

類比現實案例

可以在現實生活中找到與併發編程中的同步問題相似的案例。

例如,張三、李四和王五共同開發一個項目,張三是一名前端開發人員,他需要等待李四的開發接口任務完成再開始渲染 頁面,而李四又需要等待王五的服務開發工作完成再寫接口。

也就是說,任務之間是存在依賴關係的,前面的任務完成後,才能執行後面的任務。

在現實生活中,這種任務的同步,更多的是靠人與人之間的交流和溝通來實現的。例如,王五的服務開發任務完成了,告訴李四,李四馬上開始執行開發接口任務。等李四的接口開發完成後,再告訴張三,張三馬上調用李四開發的接口將返回的數據渲染到頁面上。現實生活中 的同步模型如圖 4 所示。

 

圖 4 現實生活中的同步模型

由圖 4 可以看出,在現實生活中,張三、李四和王五的任務之間是有依賴關係的,張三渲染頁面的任務依賴李四開發接口的任務完成,李四開發接口的任務依賴王五開發服務的任務完成。

併發編程中的同步

在併發編程領域,同步機制指一個線程的任務執行完成後,通知其他線程繼續執行任務的方式,併發編程同步簡易模型如圖 5 所示。

 

圖 5 併發編程同步簡易模型

由圖 5 可以看出,在併發編程中,多個線程之間的任務是有依賴關係的。

線程 A 需要阻塞等待線程 B 執行完任務才能開始執行任務,線程 B 需要阻塞等待線程 C 執行完任務才能開始執行任務。線程 C 執行完任務會喚醒線程 B 繼續執行任務,線程 B 執行完任務會喚醒線程 A 繼續執行任務。

這種線程之間的同步機制,可以使用如下的 if 僞代碼來表示。

if(依賴的任務完成){  執行當前任務 }else{  繼續等待依賴任務的執行 }

**上述 if 僞代碼所代表的含義是:**當依賴的任務完成時,執行當前任務,否則,繼續等待依 賴任務的執行。

在實際場景中,往往需要及時判斷出依賴的任務是否已經完成,這時就可以使用 while 循 環來代替 if 判斷, while 僞代碼如下。

while(依賴的任務未完成){  繼續等待依賴任務的執行 }  執行當前任務

**上述 while 僞代碼所代表的含義是:**如果依賴的任務未完成,則一直等待,直到依賴的任務完成,才執行當前任務。

在併發編程領域,同步機制有一個非常經典的模型——生產者—消費者模型。如果隊列已滿,則生產者線程需要等待,如果隊列不滿,則需要喚醒生產者線程;如果隊列爲空,則消費者線程需要等待,如果隊列不爲空,則需要喚醒消費者。

可以使用下面的僞代碼來表示生產者—消費者模型。

生產者僞代碼

while(隊列已滿){  生產者線程等待 }  喚醒生產者

消費者僞代碼

while(隊列爲空){  消費者等待 }  喚醒消費者

在 Java 中,Semaphore、Lock、synchronized.、CountDownLatch、CyclicBarrier、Exchanger 和 Phaser 等工具類或框架實現了同步機制。

3、互斥問題

在併發編程中,互斥問題一般指在同一時刻只允許一個線程訪問臨界區的共享資源。互斥強調的是多個線程執行任務時的正確性。

類比現實案例

互斥問題在現實中的一個典型場景就是交叉路口的多輛車匯入一個單行道,如圖 6 所示。

 

圖 6 交叉路口的多輛車匯入一個單行道

從圖 6 可以看出,當多輛車經過交叉路口匯入同一個單行道時,由於單行道的入口只能容納一輛車通過,所以其他的車輛需要等待前面的車輛通過單行道入口後,再依次有序通過單行道入口。這就是現實生活中的互斥場景。

併發編程中的互斥

在併發編程中,分工和同步強調的是任務的執行性能,而互斥強調的則是執行任務的正確性,也就是線程的安全問題。

如果在併發編程中,多個線程同時進入臨界區訪問同一個共享變量,則可能產生線程安全問題,這是由線程的原子性、可見性和有序性問題導致的。

而在併發編程中解決原子性、可見性和有序性問題的核心方案就是線程之間的互斥。

例如,可以使用 JVM 中提供的 synchronized 鎖來實現多個線程之間的互斥,使用 synchronized 鎖的僞代碼如下。

修飾方法

public synchronized void methodName(){   //省略具體方法 }

修飾代碼塊

public void methodName(){   synchronized(this){      //省略具體方法   }} 
public void methodName(){   synchronized(obj){      //省略具體方法     }  } 
public void methodName(){   synchronized(ClassName.class){      //省略具體方法   }  }

修飾靜態方法

public synchronized static void staticMethodName(){   //省略具體方法 }

除了 synchronized 鎖,Java 還提供了 ThreadLocal、CAS、原子類和以 CopyOnWrite 開頭的併發容器類、Lock 鎖及讀 / 寫鎖等,它們都實現了線程的互斥機制。

本文節選自

本文節選自**《深入理解高併發編程:核心原理與案例實戰》,**主要介紹了併發編程中的三大核心問題:分工、同步和互斥,並列舉了現實生活中的場景進行類比,以便讀者理解這三大核心問題。歡迎閱讀此書瞭解更多關於併發編程的內容。

關注「ImportNew」,提升 Java 技能

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Cjkab6In_cCEaDqJUCKgSw