一分鐘理解Java公平鎖與非公平鎖
和朋友聊天他提到:ReentrantLock 的構造函數可以傳遞一個 bool 數據,true 時構造的是“公平鎖”、false 時構造的是“非公平鎖”。
我的印象中鎖是不區(qū)分類型的,所以認為這應該是 Java 發(fā)明的概念,于是就惡補了一下。
鎖的底層實現
無論什么語言在操作系統(tǒng)層面鎖的操作都會變成系統(tǒng)調用(System Call),以 Linux 為例,就是 futex 函數,可以把它理解為兩個函數: futex_wait(s),對變量 s 加鎖;futex_wake(s)釋放 s 上的鎖,喚醒其他線程。
如果你熟悉操作系統(tǒng)原理其實就是 P/V 操作。
Java 公平鎖和非公平鎖
公平鎖的 lock 操作是調用futex_wait,unlock 操作是調用futex_wake。比如下面的代碼
非公平鎖的 lock/unlock 操作會先做一次 CAS 操作然后再調用 futex_wait、futex_wake。比如下面的代碼
在上鎖之前增加了一個 CAS 原子操作,它接受三個變量可以把它理解為下面的邏輯:
***個參數的值和第二個參數不相等則返回 0 表示操作失敗否則更新為新的值。這個函數不是由代碼實現的而是 CPU 提供的一個指令,比如 Intel 的叫 cmpxchg;高級語言進行了封裝,比如 Java 的 Atomic 變量。
為什么
明白了原理再來提問為什么,在上鎖之前先通過 CAS 修改一個變量表示“我要上鎖”了看似很冗余的操作,其實它是一次自旋,如果資源很快被使用完可以提高系統(tǒng)的吞吐率??紤]下面的場景
上鎖之前的時間是 t1,上鎖之后是 t2(使用資源),釋放鎖是 t3。
現在有兩個線程,處于 t1 狀態(tài),其中 A 線程先搶到資源處于 t2 ;B 線程也會嘗試 lock,與此同時 t2 釋放了,而 lock 動作也執(zhí)行成功了 B 被掛起;系統(tǒng)繼續(xù)執(zhí)行 A 釋放成功喚醒 B 繼續(xù)執(zhí)行。
上述過程中 B 只要再多等待“一丟丟”就不用被掛起,直接獲得資源繼續(xù)執(zhí)行。非公平鎖的 CAS 操作就是為了增加一丟丟時間。
采用非公平鎖,如果系統(tǒng)中有 3 個線程執(zhí)行,A 搶到資源,C 沒有搶到處于掛起狀態(tài),此時 B 嘗試 CAS 操作,而 A 剛好釋放掉資源還沒有來得及喚醒 C,那么 B 會先搶到資源,在 C 之前執(zhí)行。這就是“非公平”的來歷,雖然 C 老老實實的等待了很長時間,但是 B 的“時機”把握的好,迅速“插隊”完成資源搶占。
總結
上鎖的過程本身也是有時間開銷的,如果操作資源的時間比上鎖的時間還短建議使用非公平鎖可以提高系統(tǒng)的吞吐率;否則就老老實實的用公平鎖。
【本文是51CTO專欄作者“邢森”的原創(chuàng)文章,轉載請聯系作者本人獲取授權】