多線程7 ReentrantLock原理

1.ReentrantLock加鎖原理

1.第一個線程進來

FairSync裏的lock方法

final void lock() {
// 加鎖成功後,修改的值
    acquire(1);
}

其抽象父類的方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

判中的第一個tryAcquire方法,

其父類方法時這樣的,但是我們要看的時fairSync(公平鎖的)

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
protected final boolean tryAcquire(int acquires) {
// 拿到當前線程
        final Thread current = Thread.currentThread();
// 拿到鎖的狀態;C = 0 就是沒有佔用鎖
        int c = getState();
// 沒有人佔用,
        if (c == 0) {
// hasQueuedPredecessors 是否有線程等待
// 當hasQueuedPredecessors返false,compareAndSetState就加鎖
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
// 設置當前線程佔用鎖
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

aqs的屬性

// 頭節點
private transient volatile Node head;
// 尾節點
private transient volatile Node tail;

// 線程的狀態(計數)
private volatile int state;

aqs的隊列裏,頭節點header線程對象一直是null,所有等待線程都是排在後面的

public final boolean hasQueuedPredecessors() {

// 隊尾
    Node t = tail; // Read fields in reverse initialization order
// 隊頭
    Node h = head;
    Node s;
// 第一個線程來時,h!=t 返回的時false,隊列還沒初始化,兩個都是null
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

再返回acquire方法

public final void acquire(int arg) {
// 如果第一個線程拿到鎖,返回true,那麼這裏第一個判斷就返回false,就結束執行
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

2.第二個線程來的時候,再次走tryAcquire

protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
// 拿到鎖的狀態;C = 1 就是沒有佔用鎖
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }

// 當C不等於0 時,判斷當前線程是否是是加鎖的那個線程,如果這個判斷進去就表示,重入鎖
        else if (current == getExclusiveOwnerThread()) {
// 這裏acquire = 1,nextc = 2
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
// 這裏設置state = 2,表示該線程進入2次,然後返回true
            setState(nextc);
            return true;
        }
// 當前面兩個判斷走完,基本就確定該線程是拿不到鎖的
        return false;
    }
}

返回後,進入acquireQueued方法 

public final void acquire(int arg) {
// 如果第一個線程拿到鎖,返回true,那麼這裏第一個判斷就返回false,就結束執行
    if (!tryAcquire(arg) &&
// 如果第二個線程在上一個操作中沒有獲取到鎖
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
private Node addWaiter(Node mode) {
// 實例化當前線程節點
    Node node = new Node(Thread.currentThread(), mode);
// 這裏tail爲空,因爲還沒初始化
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
// 進入這個方法;進行類似添加的操作,將node添加到隊列中
    enq(node);
    return node;
}

只有一個線程來的時候,並沒有走隊列的邏輯,只是判斷了隊列是否由隊頭隊尾,然後就設置了加鎖狀態了,所有當第二個線程來的時候,aqs隊列還是沒有初始化的。

private Node enq(final Node node) {
    for (;;) {
// 將tail(null)給t
        Node t = tail;
// 第一次,
        if (t == null) { // Must initialize
// new 一個node給隊頭
            if (compareAndSetHead(new Node()))
// 再把隊頭給tail(隊尾)
                tail = head;
        } else {
// 再次循環進入,將t(現在已經有實例化後的了)設置爲自己的前一個節點
            node.prev = t;
// 將自己加到隊尾,設置前一個節點的下一個節點爲自己
            if (compareAndSetTail(t, node)) {
                t.next = node;
// 返回當前節點的上一個節點
                return t;
            }
        }
    }
}

這時,可以看到,第一次for,它new 了一個隊頭,空線程對象,並且tail = head,第二次for,t = tail = head

,compareAndSetTail設置當前節點爲隊尾,t.next 執行當前線程節點,return 跳出循環。

爲什麼頭節點的線程對象要設置爲空的??

第一個線程來的時候,拿到鎖,當前線程對象已經有了,aqs裏的隊列裏不需要再保存一次,而且,隊列裏的對拿到鎖的線程對象不做任何操作,無意義。

然後結束返回:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
// addWaiter將當前節點加入到隊尾
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

通過判斷當前節點是不是在隊頭來判斷是不是被park

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
// 一直判斷
        for (;;) {
// 獲取到上一個節點
            final Node p = node.predecessor();
// 判斷是否是頭部,就嘗試加鎖
            if (p == head && tryAcquire(arg)) {
// 加鎖成功後,將頭節點設置爲當前線程節點
                setHead(node);
                p.next = null; // help GC
                failed = false;
// 返回false
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

這裏的node是上一個節點,第二個線程進來,那麼node就是頭節點,它會判斷是否是頭節點,然後tryAcquire嘗試加鎖。

第一個判斷中,加鎖失敗後,進入第二個判斷,然後進入這個方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
// Node.SIGNAL = -1,初始化後的這個狀態是0,表示等待
    if (ws == Node.SIGNAL)
  
        return true;
    if (ws > 0) {
    
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
  
// 進入這個方法,將上一個節點的watiStatus賦值爲  -1,表示上一個節點時活躍狀態,且下一個線程節點需要運行
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

waitStatus包含的狀態有:

  1. CANCELLED,值爲1,表示當前的線程被取消;

  2. SIGNAL,值爲-1,表示當前節點的後繼節點包含的線程需要運行,也就是unpark;

  3. CONDITION,值爲-2,表示當前節點在等待condition,也就是在condition隊列中;

  4. PROPAGATE,值爲-3,表示當前場景下後續的acquireShared能夠得以執行;

  5. 值爲0,表示當前節點在sync隊列中,等待着獲取鎖。

走完這個方法後,因爲循環,又會走到shouldParkAfterFailedAcquire

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
// 一直判斷
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
// 第一次進入後修改值後,再循環後又會進到這裏,這時shouldParkAfterFailedAcquire返回爲true,
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

第二次循環shouldParkAfterFailedAcquire方法返回true後,執行parkAndCheckInterrupt方法進行park,嘗試獲取鎖,然後獲取不到鎖,如果獲取不到就阻塞,返回中斷標記並重置狀態

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

如果park拿到鎖了,就加鎖成功,那麼interrupted就是false,就不會進入selfInterrupt方法。

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

然後上面的步驟再來一遍進入shouldParkAfterFailedAcquire

而非公平鎖的lock,再加鎖失敗後就進入排隊,就和上面的公平鎖是一樣的流程。

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
// 如果加鎖失敗,就和公平鎖的排隊一樣
        acquire(1);
}

大概畫了一個AQS結構:

 

2.ReentrantLock解鎖過程

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
// 嘗試釋放鎖
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
// 如果state = 1(線程狀態非重入鎖),c = 0
    int c = getState() - releases;
// 這裏判斷當前線程是否是加鎖的線程
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
// 修改狀態
        free = true;
// 修改加鎖的線程爲null
        setExclusiveOwnerThread(null);
    }
// 修改狀態爲 c = 0
    setState(c);
// 返回true
    return free;
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
// 判斷頭節點是否爲null,且該線程節點是否是活躍狀態,如果只有一個線程時,沒有等待隊列,head = null,h = null,
// 如果時多於1個線程時,h != null,判斷waitStatus狀態,是否時隊頭,因爲在添加等待隊列的時候,會把上一個節點的waitStatus改爲-1,那麼waitStatus = 0 就是表示時隊尾
        if (h != null && h.waitStatus != 0)
// 釋放
            unparkSuccessor(h);
// 返回true
        return true;
    }
    return false;
}

進入unparkSuccessor這個方法表示,有多個線程在排隊,且該線程不在隊尾,時隊頭waitStatus = -1

private void unparkSuccessor(Node node) {
// 頭結點的狀態
    int ws = node.waitStatus;
    if (ws < 0)
// 修改對頭節點的waitStaus = 0
        compareAndSetWaitStatus(node, ws, 0);
// 獲取下一個節點
    Node s = node.next;
// 下一個節點不可能爲空,因爲頭節點爲-1,是因爲它的下一個節點修改的,waitStaus > 0 表示線程被取消,也不可能,所以不會進入
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
// 進入這個方法
        LockSupport.unpark(s.thread);
}

假設:s == null || s.waitStatus > 0成立,那麼這個對立的節點可能存在人爲的操作致使隊列發生變化,

if (s == null || s.waitStatus > 0) {
// 設置該節點爲null
        s = null;
// 條件從尾節點開始,只要不等於空,且不等於當傳進來的節點也就是異常節點,就往前遍歷
        for (Node t = tail; t != null && t != node; t = t.prev)
// 找到等待狀態 = -1的,也就是持有鎖定節點
            if (t.waitStatus <= 0)
                s = t;
    }

調用unsafe的unpark方法

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

這個方法執行後就會在執行park方法的地方繼續執行,即第一個線程持有鎖是,第二個線程也來排隊,

private final boolean parkAndCheckInterrupt() {
// 在這裏被阻塞了,
    LockSupport.park(this);
    return Thread.interrupted();
}
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
// 那麼當調用unpark方法後,會從這裏開始執行。
    setBlocker(t, null);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章