圖解:為什么非公平鎖的性能更高?
作者 | 王磊
來源 | Java中文社群(ID:javacn666)
轉載請聯系授權(微信ID:GG_Stone)
在 Java 中 synchronized 和 ReentrantLock 默認使用的都是非公平鎖,而它們采用非公平鎖的原因都是一致的,都是為了提升程序的性能。那為什么非公平鎖就能提升性能呢?接下來我們一起來看。
非公平鎖
非公平鎖:每個線程獲取鎖的順序是隨機的,并不會遵循先來先得的規(guī)則,任何線程在某時刻都有可能直接獲取并擁有鎖。

這就好比磊哥去加油,到了加油站之后發(fā)現前面有人在加,于是我就在車里刷起了抖音,過了一會,前面的車加完油走了,但磊哥沒注意到,還在車里愉快的刷著抖音。然而此時加油站又來了一輛車,發(fā)現有空閑的油槍,于是就搶先在磊哥之前把油加了。這里的油槍就是鎖,沒有按照到達的先后順序得到油槍,這就是非公平鎖。
公平鎖
公平鎖:每個線程獲取鎖的順序是按照線程訪問鎖的先后順序獲取的,最前面的線程總是最先獲取到鎖。

這就好像上高速排隊過收費站一樣,所有的車要排隊等待通行,最先來的車最先通過收費站。
性能對比
公平鎖和非公平鎖的性能測試結果如下,以下測試數據來自于《Java并發(fā)編程實戰(zhàn)》:

從上述結果可以看出,使用非公平鎖的吞吐率(單位時間內成功獲取鎖的平均速率)要比公平鎖高很多。
性能分析
以上測試數據雖然說明了結果,但并不能說明為什么非公平鎖的性能會更高?所以,接下來,我們通過分析公平鎖和非公平的執(zhí)行流程,來得到這個問題的答案。
公平鎖執(zhí)行流程獲取鎖時,先將線程自己添加到等待隊列的隊尾并休眠,當某線程用完鎖之后,會去喚醒等待隊列中隊首的線程嘗試去獲取鎖,鎖的使用順序也就是隊列中的先后順序,在整個過程中,線程會從運行狀態(tài)切換到休眠狀態(tài),再從休眠狀態(tài)恢復成運行狀態(tài),但線程每次休眠和恢復都需要從用戶態(tài)轉換成內核態(tài),而這個狀態(tài)的轉換是比較慢的,所以公平鎖的執(zhí)行速度會比較慢。
用戶態(tài) & 內核態(tài)
用戶態(tài)(User Mode):當進程在執(zhí)行用戶自己的代碼時,則稱其處于用戶運行態(tài)。內核態(tài)(Kernel Mode):當一個任務(進程)執(zhí)行系統調用而陷入內核代碼中執(zhí)行時,我們就稱進程處于內核運行態(tài),此時處理器處于特權級最高的內核代碼中執(zhí)行。

為什么分內核態(tài)和用戶態(tài)?
假設沒有內核態(tài)和用戶態(tài)之分,程序就可以隨意讀寫硬件資源了,比如隨意讀寫和分配內存,這樣如果程序員一不小心將不適當的內容寫到了不該寫的地方,很可能就會導致系統崩潰。
而有了用戶態(tài)和內核態(tài)的區(qū)分之后,程序在執(zhí)行某個操作時會進行一系列的驗證和檢驗之后,確認沒問題之后才可以正常的操作資源,這樣就不會擔心一不小心就把系統搞壞的情況了,也就是有了內核態(tài)和用戶態(tài)的區(qū)分之后可以讓程序更加安全的運行,但同時兩種形態(tài)的切換會導致一定的性能開銷。
非公平鎖執(zhí)行流程
當線程獲取鎖時,會先通過 CAS 嘗試獲取鎖,如果獲取成功就直接擁有鎖,如果獲取鎖失敗才會進入等待隊列,等待下次嘗試獲取鎖。這樣做的好處是,獲取鎖不用遵循先到先得的規(guī)則,從而避免了線程休眠和恢復的操作,這樣就加速了程序的執(zhí)行效率。
比如前幾天磊哥去一個小營業(yè)廳辦理網絡移機的業(yè)務,去了之后發(fā)現前面有人在辦業(yè)務,于是磊哥就告訴前面(辦理業(yè)務)的小姐姐,“我門口休息一下,您等會辦理完業(yè)務,麻煩去門口叫一下我”,小姐姐人也比較好,一口就答應下來了。
但在小姐姐辦完業(yè)務之后叫我,和我回到柜臺辦理業(yè)務之間,是有一段空閑時間的,這和等待隊列中的線程被喚醒和恢復執(zhí)行之間是有一段空閑時間是一樣的,而在這個空閑的時間中,營業(yè)廳又來了一個老李頭來交話費,等老李交完話費,我恰好也剛回來可以直接辦理業(yè)務了,這樣就是一個“三贏”的局面。
老李頭不用排在我后面等著繳話費,我也不用等老李頭交完話費再辦理移機,而且在單位時間內提高了營業(yè)員辦理業(yè)務的效率,她也能早早的回家,這就是所謂的“三贏”。在更短的時間內執(zhí)行更多的任務,這就是非公平鎖的優(yōu)勢。


總結
本文我們介紹了公平鎖和非公平鎖的定義以及執(zhí)行流程,從二者執(zhí)行流程的細節(jié)可以看出,非公平鎖因為不用按(順)序執(zhí)行,所以后來的鎖也可以直接嘗試獲得鎖,沒有了阻塞和恢復執(zhí)行的步驟,所以它的性能會更高。