Java鎖及AbstractQueuedSynchronizer源碼分析

一,Lock

二,關於鎖的幾個概念

三,ReentrantLock類圖

四,幾個重要的類

五,公平鎖獲取

5.1 lock

5.2 acquire

5.3 tryAcquire

    5.3.1 hasQueuedPredecessorsnode

    5.3.2 compareAndSetState併發

    5.3.3 setExclusiveOwnerThread工具

    5.3.4 getExclusiveOwnerThreadui

5.4 addWaiter

5.5 acquireQueued

    5.5.1 shouldParkAfterFailedAcquirethis

    5.5.2 parkAndCheckInterruptspa

5.6 selfInterrupt

Lock 

Lock實現提供了比使用synchronized方法和synchronized語句塊擴展性更好的鎖操做,他們容許更靈活地構建,能夠有至關不一樣的屬性,而且能夠支持多個相關的Condition對象。鎖是一個控制被多個線程共享的資源訪問的工具,通常來講,鎖對共享資源提供排它地訪問,一個時間內只能一個線程能夠獲取鎖而且要求獲取鎖的線程是全部對共享資源的訪問線種中的第一個。然而,一些鎖能夠容許對共享的資源併發訪問,好比ReadWriteLock的讀鎖線程

關於鎖的幾個概念

鎖重入
就是一個線程獲取鎖以後,這個線程還能夠再次獲取相同鎖。
公平鎖
獲取鎖的過程採用相似排隊的機制,排在前面的線程先獲取鎖。
非公平鎖
獲取鎖的過程,不排隊,只要沒有線程持有鎖,就能夠獲取鎖。

ReentrantLock類圖

 

幾個重要的類

從上圖咱們看到,
Lock是一個接口,他定義了關於鎖的基本操做方法。
ReentrantLock實現了Lock接口,它是一個可重入的獨佔鎖,與synchronized方法和語句塊具備相同的行爲和語義。
Sync是一個ReentrantLock中的抽象類。它和ReentrantLock是組合關係。
Sync繼承於AbstractQueuedSynchronizer
AbstractQueuedSynchronizer是一個抽象類。它提供了不少關於鎖的基本操做。

Sync有兩個子類。是NonfairSyncFairSync。分別表示非公平鎖和公平鎖的實現。
咱們下面將會主要看實現的lock方法。
咱們以ReentrantLock做爲入口來看鎖獲取的過程。它經常使用的用法以下。
class X {
    private final ReentrantLock lock = new ReentrantLock();
    public void m() {
      lock.lock();  // block until condition holds
      try {
        // ... method body
      } finally {
        lock.unlock()
      }
    }
  }}

 


先看看ReentrantLock獲取鎖的過程
public void lock() {
        sync.lock();
    }

它直接調用的sync對象的lock()方法。code

Sync類提供了公平鎖和非公平鎖的公共操做。它的lock方法是一個抽象方法。具體實如今公平鎖
FairSync和非公平鎖實現NonfairSync中實現。首先先看公平鎖版本的實現


final void lock() {
       acquire(1);
}
  • acquire

acquire(1)這方法是在在AbstractQueuedSynchronizer中實現對象

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

它大體作了以下幾個事情。blog

1 tryAcquire() 就是試圖獲取鎖,若是成功就結束

2 addWaiter 新建表示當前線程的一個節點。加入隊列當中

3 acquireQueued 若是試圖獲取失敗,就加入隊列中等待

4 selfInterrupt(),本身給本身一箇中斷。

 

  • tryAcquire

這個方法在AbstractQueuedSynchronizer中是一個抽象的方法,具體實現就是子類中,這裏就是在

ReentrantLock的FairSync類裏面。
protected final boolean tryAcquire(int acquires) {
       1.獲取當前線程
final Thread current = Thread.currentThread();
       2.當前state值
int c = getState(); if (c == 0) { //若是c等於0,表示當前沒有線程佔有鎖,能夠直接獲取鎖 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { //若是隊列中前面沒有其它等待的線程,就把狀態state值設置爲acquires的值,這裏是1. setExclusiveOwnerThread(current); //把當前鎖的持有線程設置爲當前線程 return true; } } else if (current == getExclusiveOwnerThread()) { //若是c不是0,說明已經有線程持有這個鎖,就看持有鎖的線程是否是當前線程,若是是就把state的值加1。而後更新它的值 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
  • hasQueuedPredecessors
public final boolean hasQueuedPredecessors() {
        Node t = tail; // 隊列的尾
        Node h = head; // 隊列的頭
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

這裏主要的判斷邏輯,就是若是頭和尾不相等,說明這個隊列就不爲空,而且隊列的頭的下一下節點不是當前線程,就說明隊列中有前繼節點。

可是我第一次看這個代碼的時候,有個疑問,爲何頭節點的next爲null的時候,也說明有前繼節點呢?

 

  • compareAndSetState

這個一個CAS操做,就是原子地設置state的值,若是指望值是0,就設置state的值爲傳入的值。

  • setExclusiveOwnerThread
這個方法是設置當前鎖的持有線程爲當前線程,這個方法是在AbstractOwnableSynchronizer抽象類裏定義的,它是一個表示這一個鎖是由那個線程獨佔持有的輔助類。
AbstractQueuedSynchronizer類繼承了這個類。


  • getExclusiveOwnerThread
若是試圖獲取鎖不成功,就進行下一步,首先就是把這個線程加入到隊列中,
addWaiter
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode); //首先建立一個表示當前線程的節點 // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) { //首先若是隊列的尾節點不爲null,就直接把新加節點加入到尾節點後面,把新加入節點設置爲尾節點,
            node.prev = pred;//把新加入的節點的前繼節點指向尾節點
            if (compareAndSetTail(pred, node)) {//設置新加入的節點爲尾節點
                pred.next = node;//以前的尾結點的後繼節點指向新加入的節點
                return node;//返回新加入的節點
            }
        }
        enq(node); 若是尾結爲空,就調用這個方法新增節點
        return node;
    }
這個方法的入參是Node.EXCLUSIVE。表示這個節點正在以排它模式等待,也就是說鎖是排它鎖。
注意上面的是先設置一個節點的prev節點,而後設置節點的next節點。因此在上面判斷前面是否有等待的節點時,若是next爲null時也認爲是有等待的節點,可能判斷的時候,正好有一個節點正在入隊。還沒設置
prev的next節點的值。我是這麼理解的,


private Node enq(final Node node) {
        for (;;) {//自旋,直到成功把節點加入到隊列中
            Node t = tail;
            if (t == null) { //若是尾節點爲null,說明這個隊列尚未初始化
                if (compareAndSetHead(new Node())) //new一個節點,並設置爲這個節點爲頭節點,注意頭節點是一個佔位節點,它是一個空節點。
                    tail = head;
            } else {//若是頭節點不爲空
                node.prev = t;//把當前節點的前繼設置爲尾節點 if (compareAndSetTail(t, node)) {//把當前節點設置爲尾節點
                    t.next = node;//把原來尾節點的後繼設置爲當前節點 return t;
                }
            }
        }
    }
當先節點加入到隊列中,就調用下面的方法,它是
  • acquireQueued
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; // 把原來的頭節點的next節點置爲null。爲了更快的垃圾回收掉。
                    failed = false;
                    return interrupted;//返回是否中斷過的標示
                }
          //若是獲取失敗,就判斷是否須要阻塞當前線程,直到下一個成功獲取鎖的線程的喚醒。
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
 
  • shouldParkAfterFailedAcquire,判斷在獲取鎖失敗後,是否須要阻塞當前線程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus; //前繼結點的waitStatus值 if (ws == Node.SIGNAL)//若是值是Node.SIGNAL,表示前一個節點被釋放後,就會喚醒這個線程,因此這個線程能夠阻塞,因此返回true。 /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {//若是值大於0,說明是被取消了。而後就往前找,直到找到一個不是被取消的前繼節點 /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

 

 
  • parkAndCheckInterrupt
若是須要阻塞 就調用這個方法阻塞當前線程。並返回當前線程是否被中斷過。若是須要阻塞而且被中斷過。就設置中斷狀態爲true。

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

 

 
  • selfInterrupt

若是嘗試獲取失敗,在隊列中等待獲取鎖時被中斷過。就調用這個方法給本身一箇中斷

static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

 

二 非公平鎖獲取
爲何是非公平呢,是由於獲取鎖的時候。不會去排隊,若是沒有線程擁有鎖。就能夠獲取鎖成功。若是不成功。他其實仍是會和公平鎖同樣來獲取鎖。
final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
 

1首先調用 compareAndSetState來設置狀態的值。若是成功。就說明獲取到鎖,並設置當前線程爲擁有鎖的線程。

不然就就和公平鎖的獲取方式同樣了。

三 鎖釋放

 
Lock接口裏定義了unlock方法。

 
ReentrantLock 的unlock方法,他調用sync的release方法。它是在AbstractQueuedSynchronizer類裏實現。
 
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;
 }
相關文章
相關標籤/搜索