[圖解Java]ReentrantLock重入鎖

圖解ReentrantLock

0. demo

我先給出一個demo, 這樣你們就能夠根據我給的這段代碼, 邊調試邊看源碼了. 仍是那句話: 注意"My" , 我把ReentrantLock類 更名爲了 "MyReentrantLock"類 , "Lock"類 更名爲了"MyLock"類. 你們粘貼個人代碼的時候, 把相應的"My"都去掉就行了, 不然會編譯報錯哦.java

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 也申請失敗, 進入到等待隊列中. ui

隨後釋放了鎖1, 而後鎖2就獲取到鎖了. 而後釋放了鎖2, 鎖3就獲取到鎖了...而後是鎖4.  大概就是這個使用. 用個人這段代碼配合着debug, 能夠很清楚地調試出代碼的執行流程.線程

1. 開始圖解ReentrantLock 

一個ReentrantLock()實例裏只有一個sync成員變量.debug

假設我們建立了一個公平鎖, 那麼sync是FairSync類的實例.3d

sync實例裏面有四個成員變量.指針

分別表示:調試

          1. state - 鎖計數器blog

          2. exclusiveOwnerThread - 鎖的持有線程隊列

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

          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.

此時, 鎖並無釋放, 仍是被線程1持有.

 

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

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. 之後等待隊列的頭結點就不須要從新初始化了.

相關文章
相關標籤/搜索