天天看點

[圖解Java]ReentrantLock重入鎖

圖解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, 可以很清楚地調試出代碼的執行流程.

[圖解Java]ReentrantLock重入鎖

1. 開始圖解ReentrantLock 

一個ReentrantLock()執行個體裡隻有一個sync成員變量.

[圖解Java]ReentrantLock重入鎖

假設咱們建立了一個公平鎖, 那麼sync是FairSync類的執行個體.

sync執行個體裡面有四個成員變量.

分别表示:

          1. state - 鎖計數器

          2. exclusiveOwnerThread - 鎖的持有線程

          3. head - `等待隊列`的頭結點.

          4. tail - 指向`等待隊列`的最後一個元素

[圖解Java]ReentrantLock重入鎖

現在鎖是空閑狀态.

當線程1申請了鎖, 會把state置為1. 然後把鎖的exclusiveOwnerThread指向自己(線程1). 這就算是持有鎖了.其他線程無法再擷取鎖了.隻能等線程1釋放.

[圖解Java]ReentrantLock重入鎖

 如果線程1在此對這個鎖執行了lock()方法呢? 

那麼就是鎖的重入了, 也就是說這個線程再次進入(擷取)了這個鎖 會讓state+1.

[圖解Java]ReentrantLock重入鎖

 再重入呢?   那就再加1....

可以重入多少次呢?   可以重入, 直到整形int溢出為止...

[圖解Java]ReentrantLock重入鎖

 接下來, 線程1還沒釋放鎖呢, 線程2就想擷取鎖了. 那麼會發生什麼呢:

把線程2封裝為一個Node類型的節點. 然後打算把這個Node放到`等待隊列`中去.

這個時候`等待隊列`才會被建立, 因為這個時候才需要`等待隊列`, 這種叫懶初始化.

這個時候, `等待隊列`的頭結點産生了. 然後把`等待隊列`的tail也指向head.

head或者tail 不為null, 表示`等待隊列`被創立了.

head==tail 表示, `等待隊列`為空, 裡面沒有`有效元素`.

[圖解Java]ReentrantLock重入鎖

 `等待隊列`有了. 線程2對應的Node也有了. 就差把這個Node插入到隊尾了.

首先讓tail指向線程2對應的Node.

然後分别維護兩個Node的前驅和後繼.(看下面紫色箭頭)

[圖解Java]ReentrantLock重入鎖

 已經将線程2對應的Node插入到`等待隊列`的尾部了, 接下來讓線程1對應的Node裡的waitState等于-1

[圖解Java]ReentrantLock重入鎖

 之後線程2就可以安心的挂起了. 等線程1完全釋放鎖的時候,  就會喚醒線程2了.

為什麼說是`完全釋放`呢? 因為鎖的的state現在等于3. 需要線程1 unlock()釋放3次鎖, 才算是完全釋放.

[圖解Java]ReentrantLock重入鎖

 接下來, 線程1還沒釋放鎖呢, (線程2也沒輪到鎖呢). 線程3就想擷取鎖了. 那麼會發生什麼呢:

首先會建立一個線程3對應的Node節點.

[圖解Java]ReentrantLock重入鎖

 然後讓尾指針tail指向這個最新的Node.

然後維護前驅和後繼(紫色箭頭), 來維持雙向連結清單.

[圖解Java]ReentrantLock重入鎖

 接下來就會讓新節點的前驅節點的waitStatus = -1.

-1表示, 有下一個節點等待被喚醒. 

[圖解Java]ReentrantLock重入鎖

 然後線程3就可以安心的挂起了.

等線程2 搶到鎖, 用完了釋放後, 就會去喚醒線程3.

[圖解Java]ReentrantLock重入鎖

咱們讓線程1 unlock() 一次.

state減1了.

此時, 鎖并沒有釋放, 還是被線程1持有.

[圖解Java]ReentrantLock重入鎖

咱們再讓線程1 unlock() 一次.

state減1了. 但仍然大于0.

[圖解Java]ReentrantLock重入鎖

state減1了. 這回state等于0了. 表示完全釋放了鎖.

exclusiveOwnerThread也置為了null, 表示目前的鎖不被任何線程持有.

[圖解Java]ReentrantLock重入鎖

準備喚醒下一個, 也就是`等待隊列`的第一個元素(線程2)

[圖解Java]ReentrantLock重入鎖

線程2被喚醒

[圖解Java]ReentrantLock重入鎖

然後鎖的state被置為了1.

鎖的exclusiveOwnerThread指向了線程2. 表示目前鎖被線程2持有了.

[圖解Java]ReentrantLock重入鎖

 既然線程1已經完全釋放鎖了. 那麼就換線程2來當`等待隊列`的頭結點.

是以此時, 頭結點的含義就是: 目前持有鎖的線程對應的Node結點. 

[圖解Java]ReentrantLock重入鎖

 然後斷開相應的前驅和後繼, 讓線程1對應的Node完全脫離`等待隊列` .

[圖解Java]ReentrantLock重入鎖

到此, 線程1釋放後, 線程2 擷取鎖的步驟就都執行完了. 

接下來, 咱們讓線程2釋放鎖.

state減1後等于0了.

于是鎖就完全釋放了. exclusiveOwnerThread就被置為null了.

[圖解Java]ReentrantLock重入鎖

 然後是waitStatus被改回了0. 線程2對應的Node馬上就要離開`等待隊列`了

[圖解Java]ReentrantLock重入鎖

 線程3被喚醒. 

[圖解Java]ReentrantLock重入鎖

 讓state=1, 并把鎖的exclusiveOwnerThread指向自己. 表示線程3自己獨占了這把鎖.

[圖解Java]ReentrantLock重入鎖

 修改head指針, 并斷開相應的前驅和後繼連結, 讓線程2對應的Node徹底離開`等待隊列`

[圖解Java]ReentrantLock重入鎖

最後, 咱們讓線程3釋放鎖.

state歸零.

exclusiveOwnerThread清空.

鎖空閑.

而head和tail仍然指向原先的Node. 以後等待隊列的頭結點就不需要重新初始化了.

[圖解Java]ReentrantLock重入鎖

---------------------------------------------------------

學如不及,猶恐失之