圖解Java的ReentrantLock重入鎖的擷取和釋放鎖的流程.
圖解ReentrantLock
0. demo
我先給出一個demo, 這樣大家就可以根據我給的這段代碼, 邊調試邊看源碼了. 還是那句話: 注意"My" , 我把ReentrantLock類 改名為了 "MyReentrantLock"類 , "Lock"類 改名為了"MyLock"類. 大家粘貼我的代碼的時候, 把相應的"My"都去掉就好了, 否則會編譯報錯哦.
import java.util.Scanner;
import java.util.function.Supplier;
public class Main {
static final Scanner scanner = new Scanner(System.in);
static volatile String cmd = "";
private static MyReentrantLock lock = new MyReentrantLock(true);
public static void main(String[] args) {
for (String name : new String[]{"1", "2", "3", "4", "5", "6"})
new Thread(() -> func(() -> lock, name)).start();
while (scanner.hasNext()) {
cmd = scanner.nextLine();
}
}
public static void func(Supplier<MyLock> myLockSupplier, String name) {
blockUntilEquals(() -> cmd, "lock " + name);
myLockSupplier.get().lock();
System.out.println("擷取了" + name + "号鎖");
blockUntilEquals(() -> cmd, "unlock " + name);
myLockSupplier.get().unlock();
System.out.println("釋放了" + name + "号鎖");
}
private static void blockUntilEquals(Supplier<String> cmdSupplier, final String expect) {
while (!cmdSupplier.get().equals(expect))
quietSleep(1000);
}
private static void quietSleep(int mills) {
try {
Thread.sleep(mills);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用例子在下面. 首先線程1申請了鎖, 成功申請. 然後線程2申請了鎖, 未申請到, 進入等待隊列中. 線程3 和 線程4 也申請失敗, 進入到等待隊列中.
随後釋放了鎖1, 然後鎖2就擷取到鎖了. 然後釋放了鎖2, 鎖3就擷取到鎖了...然後是鎖4. 大概就是這個使用. 用我的這段代碼配合着debug, 可以很清楚地調試出代碼的執行流程.

1. 開始圖解ReentrantLock
一個ReentrantLock()執行個體裡隻有一個sync成員變量.
假設咱們建立了一個公平鎖, 那麼sync是FairSync類的執行個體.
sync執行個體裡面有四個成員變量.
分别表示:
1. state - 鎖計數器
2. exclusiveOwnerThread - 鎖的持有線程
3. head - `等待隊列`的頭結點.
4. tail - 指向`等待隊列`的最後一個元素
現在鎖是空閑狀态.
當線程1申請了鎖, 會把state置為1. 然後把鎖的exclusiveOwnerThread指向自己(線程1). 這就算是持有鎖了.其他線程無法再擷取鎖了.隻能等線程1釋放.
如果線程1在此對這個鎖執行了lock()方法呢?
那麼就是鎖的重入了, 也就是說這個線程再次進入(擷取)了這個鎖 會讓state+1.
再重入呢? 那就再加1....
可以重入多少次呢? 可以重入, 直到整形int溢出為止...
接下來, 線程1還沒釋放鎖呢, 線程2就想擷取鎖了. 那麼會發生什麼呢:
把線程2封裝為一個Node類型的節點. 然後打算把這個Node放到`等待隊列`中去.
這個時候`等待隊列`才會被建立, 因為這個時候才需要`等待隊列`, 這種叫懶初始化.
這個時候, `等待隊列`的頭結點産生了. 然後把`等待隊列`的tail也指向head.
head或者tail 不為null, 表示`等待隊列`被創立了.
head==tail 表示, `等待隊列`為空, 裡面沒有`有效元素`.
`等待隊列`有了. 線程2對應的Node也有了. 就差把這個Node插入到隊尾了.
首先讓tail指向線程2對應的Node.
然後分别維護兩個Node的前驅和後繼.(看下面紫色箭頭)
已經将線程2對應的Node插入到`等待隊列`的尾部了, 接下來讓線程1對應的Node裡的waitState等于-1
之後線程2就可以安心的挂起了. 等線程1完全釋放鎖的時候, 就會喚醒線程2了.
為什麼說是`完全釋放`呢? 因為鎖的的state現在等于3. 需要線程1 unlock()釋放3次鎖, 才算是完全釋放.
接下來, 線程1還沒釋放鎖呢, (線程2也沒輪到鎖呢). 線程3就想擷取鎖了. 那麼會發生什麼呢:
首先會建立一個線程3對應的Node節點.
然後讓尾指針tail指向這個最新的Node.
然後維護前驅和後繼(紫色箭頭), 來維持雙向連結清單.
接下來就會讓新節點的前驅節點的waitStatus = -1.
-1表示, 有下一個節點等待被喚醒.
然後線程3就可以安心的挂起了.
等線程2 搶到鎖, 用完了釋放後, 就會去喚醒線程3.
咱們讓線程1 unlock() 一次.
state減1了.
此時, 鎖并沒有釋放, 還是被線程1持有.
咱們再讓線程1 unlock() 一次.
state減1了. 但仍然大于0.
state減1了. 這回state等于0了. 表示完全釋放了鎖.
exclusiveOwnerThread也置為了null, 表示目前的鎖不被任何線程持有.
準備喚醒下一個, 也就是`等待隊列`的第一個元素(線程2)
線程2被喚醒
然後鎖的state被置為了1.
鎖的exclusiveOwnerThread指向了線程2. 表示目前鎖被線程2持有了.
既然線程1已經完全釋放鎖了. 那麼就換線程2來當`等待隊列`的頭結點.
是以此時, 頭結點的含義就是: 目前持有鎖的線程對應的Node結點.
然後斷開相應的前驅和後繼, 讓線程1對應的Node完全脫離`等待隊列` .
到此, 線程1釋放後, 線程2 擷取鎖的步驟就都執行完了.
接下來, 咱們讓線程2釋放鎖.
state減1後等于0了.
于是鎖就完全釋放了. exclusiveOwnerThread就被置為null了.
然後是waitStatus被改回了0. 線程2對應的Node馬上就要離開`等待隊列`了
線程3被喚醒.
讓state=1, 并把鎖的exclusiveOwnerThread指向自己. 表示線程3自己獨占了這把鎖.
修改head指針, 并斷開相應的前驅和後繼連結, 讓線程2對應的Node徹底離開`等待隊列`
最後, 咱們讓線程3釋放鎖.
state歸零.
exclusiveOwnerThread清空.
鎖空閑.
而head和tail仍然指向原先的Node. 以後等待隊列的頭結點就不需要重新初始化了.
---------------------------------------------------------
學如不及,猶恐失之