理解阻塞、非阻塞、同步、非同步
首先說明,這些都是在特點場景下或者相對情況的詞彙,OK,接下來開門見山。
阻塞
可以很直觀的理解,就如節假日高速路出口收費站一樣,上圖片:
9個收費亭,同時來了一大波車,這時候同一時刻只能有9輛車在收費,剩下的車都在只能在後面排隊等待,這就是現實中很直觀的阻塞現象。這9個收費亭,就是一個瓶頸,或許畫為這樣更符合大家對瓶頸二字的理解:
第1張圖中,高速公路源源不斷的車輛到來,和第二張圖的效果其實表示一樣。
OK,看圖明白了現象,分析一下為什麼會阻塞?
- 數量上:
- 到來車輛數——大量
- 收費站數——小於等於9個
結論:在要過卡的汽車數量大於收費亭數量時,就會有阻塞現象。
- 速度上:
- 到來車輛速度——快速
- 收費站過卡速度——慢
結論:在收費站過卡速度比車輛到來的速度慢時,就會有阻塞現象。
綜合起來就是:因為量差和速度差,導致阻塞現象。
思考問題,為什麼會有量差?
因為有些資源是有限的,是很難避免的,高速公路出口區域的大小有限,收費亭的個數會根據合理的規劃設立,即使設立了1千個收費亭,從高速路到來的汽車跑到距離最遠的那個收費亭也是相當遠,沒有車主願意跑那麼遠去收費,它就形同虛設,有效收費亭數就還是一個相對很小的數量。同時,還需要考慮成本因素。
在程式裡,比如資料庫連線池裡的連線是有限的,比如10條連線,但1毫秒內需要做1000個查詢,就會形成阻塞現象。
而速度差是客觀存在的,收費亭還需要經過不斷的發展,才能達到和高速公路相匹配的速度,但收費亭還有一個作用就是讓高速的車輛減速下來,去匹配非高速公路的速度。
在程式裡,資料庫查詢,需要經過網路IO和磁碟IO,相同的內容怎麼都比在本機記憶體中直接檢索出來要慢。
阻塞,其實是一個客觀存在的現象,它本質上是沒法繞開的。
既然繞不開,那……非阻塞又是什麼?
非阻塞
還是上面的例子,車輛經過高速路收費亭,非阻塞更像是改版的ETC,車輛進高速,掃一下車牌登記一下,車輛離開高速,掃一下車牌登記一下,然後車輛離開了,開出個幾百米後車主手機才收到ETC被扣費的簡訊,此時高速路收費才算完成。整個過程,停留的時間很短,如果車牌識別效率非常高,甚至可以把車卡的杆去掉,這樣車輛就無需停留。
無需停留即速度與車輛到來速度相匹配,即沒有阻塞現象。
那是真的沒有阻塞了嗎?怎麼可能,只是從車的角度來看,車確實不阻塞了,但從整個收費程式來看,車輛跑出幾百米後才收費成功,就表示實際上自動扣費的速度比較慢,阻塞範圍縮小到了自動扣費上。
把阻塞範圍縮小,縮短主體停留時間,就是非阻塞要做的事情。
到這裡,先記住這個結論,先折起一小部分內容留最後總結聯絡上下文……
同步
下班回家到家門口的時候,開門經過以下步驟:
- 掏鑰匙(還需要從幾百把鑰匙裡挑選鑰匙請忽略鑰匙的步驟)
- 插入門鎖孔(磁卡鎖、指紋鎖、人臉鎖等,請積極回憶用鑰匙的日子)
- 旋轉鑰匙,開門
正常來說,三個步驟是順序依賴的,這三步驟你怎麼換人分著做,都會等待前一個步驟完成。
這時候,如果沒有別的事情干擾,基本上我們會一個人去完成整個開門的事情,因為換人,也需要時間。
開門的人,看作一個主體;整個開門過程,可以看作一個事務。那麼:
一個主體獨自完成一個事務,便可以認為這個過程是同步的。
在程式裡,給員工張三發一個節日祝福簡訊,步驟相似:
public static void main(String[] args) {
// 給員工張三發一個節日祝福簡訊,步驟相似:
// 1. 先把員工張三的資訊查詢出來
Employee employee = findEmployee("張三");
// 2. 編輯簡訊:”祝張三先生節日快樂,闔家幸福!“
String message = "祝張三" + employee.getGender() + "節日快樂,闔家幸福!";
// 3. 呼叫簡訊傳送API傳送簡訊內容到員工的手機號碼
sendMessage(employee.getPhone(), message);
}
- 先把員工張三的資訊查詢出來
- 編輯簡訊:”祝張三先生節日快樂,闔家幸福!“
- 呼叫簡訊傳送API傳送簡訊內容到員工的手機號碼
整個事務都在一條執行緒裡順序完成,則屬於同步操作。
同步的核心,是一個主體。主要看你把什麼定為一個主體。
非同步
接著上面,同步是一個主體做事,那麼非同步,就是多個主體做事。
比如開門的例子,如果把主體具體到手,右手在做開門這些步驟時,左手可能在摘下口罩,這時候兩件事情都不衝突,摘下口罩後,還可以撓撓頭,抓抓癢,左手可以為所欲為(左手千萬別掰斷右手)。
同一時刻,多個主體在做事,就屬於非同步。
在程式裡,執行緒1給張三發節日祝福簡訊,執行緒2給李四發節日祝福簡訊,執行緒3給王五發,完全沒有問題,為所欲為有木有。
當然,如果多個執行緒在做相同的事情,也可以叫併發。
思考問題,什麼時候建議非同步?
當多個事情沒有衝突,而你又有足夠的資源去同時展開工作時。
比如邊開門邊撓頭的例子,如果你的左手因為數錢導致短暫性發麻無力,只有右手可以活動,那麼邊開門邊撓頭只會讓你在切換這兩件事的時候花費更多的時間。
在程式碼裡,如果想要給張三同時發出去簡訊和郵件,則可以使用非同步的方式去實現:
public static void main(String[] args) {
// 給員工張三發一個節日祝福簡訊,步驟相似:
// 1. 先把員工張三的資訊查詢出來
Employee employee = findEmployee("張三");
// 開啟執行緒2去發郵件
new Thread(() -> {// 這裡邊的就是非同步操作
// 編輯郵件
String mailMessage = "祝<h3>張三</h3>" + employee.getGender() + "節日快樂,闔家幸福!";
// 傳送郵件
sendEmail(employee.getEmail(), mailMessage);
}).start();
// 2. 編輯簡訊:”祝張三先生節日快樂,闔家幸福!“
String message = "祝張三" + employee.getGender() + "節日快樂,闔家幸福!";
// 3. 呼叫簡訊傳送API傳送簡訊內容到員工的手機號碼
sendMessage(employee.getPhone(), message);
}
- 先把員工張三的資訊查詢出來
- 執行緒1(main執行緒):編輯簡訊;執行緒2:編輯郵件
- 執行緒1(main執行緒):傳送簡訊;執行緒2:傳送郵件
執行緒2在start()後,main執行緒就可以繼續往下執行了,main執行緒並不會等待執行緒2執行完成,也就是說,非同步有一個特點——非阻塞。
非同步可以加上回撥這個利器,在執行出結果時,通過回撥的方式,去反饋結果,這裡不展開細談。
總結
因為部分資源有限,所以阻塞客觀存在的,可以簡單的理解為有排隊等待的現象,就是阻塞。
非阻塞主要是把阻塞範圍縮小,或者把可以延遲完成的事情非同步完成,縮短主體停留時間。
最後回到收費亭的非阻塞例子,車輛在經過出高速的收費亭登記後,就讓另一條執行緒去執行收費操作,並不影響車輛通行,等車輛行駛出幾百米後,非同步的執行緒執行完畢,簡訊也發到了車主的手機上。
多加一些思考就能發現,因為速度是相對的,阻塞也是相對的,收費亭A的速度慢,但是對於它自己來說,它已經是全速了,它沒停過就沒有阻塞,但是高速路到來B的車因為它停下來等待了,所以阻塞須有A和B相互參照,才能看出誰是瓶頸。
同步和非同步,也是相對的,這取決於主體的粒度,應用服務裡A有100條執行緒在協同完成任務X,主體為執行緒時,他們是非同步的,但當你把整個服務A看作一個整體時,他是同步的,因為不管你內部有多少執行緒,你都只是完成了任務X,僅由一個主體,完成一個事務,就是同步。
運用這些思維,可以很好的去理解阻塞佇列、執行緒池、連線池等元件,以後有空再展開吧。
總結有點囉嗦了,就這樣……回見~
end.