源碼分析AbstractQuenedSynchronized(一)

源碼分析AbstractQuenedSynchronized(二)
源碼分析AbstractQuenedSynchronized(三)

從 ReentrantLock 的公平鎖源碼出發,分析AbstractQueuedSynchronizer 這個類如何工作

AbstractQuenedSynchronized

該類是一個抽象類,簡稱AQS,是Java併發包的基礎工具類,是實現ReentrantLock、CountDownLatch、Semaphore、FutureTask等類的基礎。

數據結構

雙向隊列,結點Node包含:pre+next+thread+waitStatus

圖片來源於https://javadoop.com/post/AbstractQueuedSynchronizer圖片來源https://javadoop.com/post/AbstractQueuedSynchronizer

    static final class Node {
        //標識結點在共享模式下
        static final Node SHARED = new Node();
        //標識結點在獨佔模式下
        static final Node EXCLUSIVE = null;

        //================WaitStatus的狀態==================//
        static final int CANCELLED =  1;//取消爭搶鎖
        static final int SIGNAL    = -1;//當前node的後繼結點對應的線程需要被喚醒
        static final int CONDITION = -2;//結點在等待隊列中,結點線程等待在Condition上
        static final int PROPAGATE = -3;//下一次共享式同步狀態獲取將會無條件地被傳播下去

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;

        volatile Thread thread;

        Node nextWaiter;//用於條件隊列
   }

模板方法

//這些模板方法的作用體現在下面對ReentrantLock的分析中
void acquire(int arg) ***->子類會覆寫其中的boolean tryAcquire(arg)方法
boolean release(int arg) ***->子類會覆寫其中的boolean tryRelease(arg)方法
Node addWaiter(Node mode)//循環CAS操作將結點加入同步隊列尾部
Node enq(final Node node)//addWaiter中的方法
boolean acquireQueued(final Node node, int arg)//循環CAS操作更新同步隊列
boolean shouldParkAfterFailedAcquire()//判斷是否需要被阻塞
boolean parkAndCheckInterrupt()//阻塞當前線程然後判斷線程等待狀態時是否被中斷
void unparkSuccessor(Node node)//喚醒後繼結點

ReentrantLock

默認非公平鎖,通過構造器參數中的布爾值來確定鎖是否公平,通過內部類Sync來管理鎖,真正的獲取鎖和釋放鎖由Sync兩個實現類來實現,
在這裏插入圖片描述

   public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLock獲得公平鎖

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        //爭鎖
        final void lock() {
            acquire(1);
        }

/*========調用父類AQS的acquire方法,注意這裏面涉及到3個方法:tryAcquire()【子類覆寫父類AQS】、addWaiter()【父類AQS】、acquireQueued()【父類AQS】,下面一一講解==========
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//調用成功則不需要排隊
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//否則將該線程包裝成結點添加到同步隊列中。Node.EXCLUSIVE=null;
            selfInterrupt();
    }
 */

        /**
         * 覆寫了父類AQS的tryAcquire方法
         * 兩種情況下說明嘗試獲得鎖成功,1.沒有線程持有鎖,也就是沒有線程在等待鎖 2.進行鎖重入
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();//獲取等待狀態

            //1.state==0,沒有線程持有鎖
            if (c == 0) {
                if (!hasQueuedPredecessors() &&//沒有線程在等待鎖
                        //嘗試CAS操作獲取鎖
                        compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);//獲取成功則標記當前線程已持有鎖
                    return true;
                }
            }
            //2.該分支說明鎖可重入了,state=state+1;
            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的addWaiter方法,該方法的功能是通過循環CAS將新的結點加入到同步隊列中,這裏面又有一個enq()方法====================
    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) {//同步隊列不爲空
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {//如果CAS操作將新的結點加入到隊列末尾則直接返回該結點
             //進入到這裏說明設置成功,tail==node
                pred.next = node;
                return node;
            }
        }
        //執行到這裏說明:要麼加入該結點前隊列爲空;要麼CAS操作沒有成功
        enq(node);
        return node;
    }
 */

/*======================父類AQS的enq方法===============================
     * 進入到這個方法只有兩種可能:1.隊列爲空 2.有線程競爭入隊
     * 採用自旋方式入隊:CAS設置tail過程中,競爭一次競爭不到,就多次競爭,總會排到的
     * @return node's predecessor
    private Node enq(final Node node) {
     //循環CAS
      for (;;) {
          Node t = tail;
          if (t == null) { // Must initialize   初始時隊列爲空,head和tail都爲空,因此第一個結點加入隊列後肯定會進入到該方法進行初始化
            if (compareAndSetHead(new Node()))
                tail = head;//新實例化一個Node對象作爲頭結點,這個時候head.waitStatus==0,表示初始狀態,
                            //!!!!!!!!!!注意沒有return!!!!!!!!!!!!!! 繼續for循環!讓多線程從head結點後面開始排隊
        } else {
            //下一個線程進來向獲取鎖,發現同步隊列中尾結點不爲空,就會插入到隊列尾進行排隊。當多個線程同時進來競爭時,通過CAS循環,將排隊操作串行化
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
 */

/*========================父類AQS的方法acquireQueued,該方法的作用是循環CAS操作不斷嘗試去更新同步隊列的head,即不斷嘗試去獲得鎖=======================================
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;//標記是否成功獲取同步狀態,初始時設置爲failed==true,即未獲取同步狀態
        try {
            boolean interrupted = false;//標記等待過程中是否被中斷過
            for (;;) {
                final Node p = node.predecessor();//p是node的前驅結點
                //如果p是head,也就是說node是阻塞隊列的第一個結點,則嘗試獲取同步狀態
                //注意:我們認爲阻塞隊列不包含head結點,head一般指的是佔有同步狀態的線程,head後面的才稱爲阻塞隊列
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;//成功獲取同步狀態
                    return interrupted;
                }
                //代碼執行到這裏,說明node嘗試獲取鎖失敗,原因有兩個:1. node不是隊頭 2.與其他線程爭取同步狀態失敗
                if (shouldParkAfterFailedAcquire(p, node) //判斷當前線程是否需要被掛起,需要掛起則返回true
                        &&
                    parkAndCheckInterrupt())//執行當前線程的掛起,調用LockSupport的park方法,等待被喚醒
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 */

/*============================父類AQS的shouldParkAfterFailedAcquire()方法==========================
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        //前驅結點狀態爲signal,則當前結點可以被掛起,等待前驅結點釋放同步狀態或者被中斷
        if (ws == Node.SIGNAL)
            return true;
        //前驅結點狀態>0,則說明前驅結點取消了排隊,因此繼續往前找,直到找到一個狀態<=0的結點作爲前驅結點
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
//              進入這個分支意味着等待狀態爲0,-2,-3
//              由於之前都沒有設置waitStatus,所以每個新的node入隊時,waitStatus都是0
//              正常情況下前驅結點是之前的tail,它的waitStatus應該是0
//              CAS操作將前驅結點的waitStatus設置爲Node.Signal(也就是-1)
//              然後從該方法中返回false,進入上層方法的for循環,又重新進入該方法從從一個分支返回true
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
 */

/*===================父類AQS的parkAndCheckInterrupt()方法=============================
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//利用LockSupport工具阻塞當前線程,只有調用unpark()或者當前線程被中斷才能夠從park()方法返回
        return Thread.interrupted();
    }
 */
    }


ReentrantLock釋放公平鎖

    public void unlock() {
        sync.release(1);

/*=================父類AQS的release()方法。裏面又有tryRelease()方法【子類Sync覆寫】和unparkSuccessor()方法=====================
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
 */

/*====================Sync類的tryRelease()方法,重寫了父類AQS的tryRelease方法==================
     protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //c==0說明完全釋放鎖
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
 */

/*============================父類AQS的unparkSuccessor()方法==========================
    private void unparkSuccessor(Node node) {//此處node爲頭結點head
        int ws = node.waitStatus;
        if (ws < 0)//頭結點waitStatus小於0則將其設爲0
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;//s爲同步隊列中的第一個結點

        //如果s爲空或者狀態大於0即取消了等待,那麼從隊列尾部向前尋找waitStatus<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;
        }
        //喚醒後繼結點,後繼結點在acquireQueued方法中通過for循環繼續執行,發現前繼結點是head從而獲取鎖
        if (s != null)
            LockSupport.unpark(s.thread);
    }
 */
    }

公平鎖和非公平鎖

公平鎖的爭鎖過程

   static final class FairSync extends Sync {
        //爭鎖
        final void lock() {
            acquire(1);
        }

/*========調用父類AQS的acquire方法,注意這裏面涉及到3個方法:tryAcquire()【子類覆寫父類AQS】、addWaiter()【父類AQS】、acquireQueued()【父類AQS】==========
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//調用成功則不需要排隊
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//否則將該線程包裝成結點添加到同步隊列中。Node.EXCLUSIVE=null;
            selfInterrupt();
    }
 */

        /**
         * 覆寫了父類AQS的tryAcquire方法
         * 兩種情況下說明嘗試獲得鎖成功,1.沒有線程持有鎖,也就是沒有線程在等待鎖 2.進行鎖重入
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();//獲取等待狀態

            //1.state==0,沒有線程持有鎖
            if (c == 0) {      【對比】非公平鎖在此處沒有對等待線程的判斷
                if (!hasQueuedPredecessors() &&//沒有線程在等待鎖
                        //嘗試CAS操作獲取鎖
                        compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);//獲取成功則標記當前線程已持有鎖
                    return true;
                }
            }
            //2.該分支說明鎖可重入了,state=state+1;
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
 }

非公平鎖的爭鎖過程

    static final class NonfairSync extends Sync {
       /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))【對比】和公平鎖相比,並沒有對鎖的等待狀態進行判斷,而是直接調用一次CAS,嘗試去獲得鎖
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

/*========調用父類AQS的acquire方法,注意這裏面涉及到3個方法:tryAcquire()【子類覆寫父類AQS】、addWaiter()【父類AQS】、acquireQueued()【父類AQS】==========
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//調用成功則不需要排隊
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//否則將該線程包裝成結點添加到同步隊列中。Node.EXCLUSIVE=null;
            selfInterrupt();
    }
 */

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }


        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {【對比】和公平鎖相比,這裏沒有對阻塞隊列進行判斷,即根本不管有沒有線程在等待鎖
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

    }

總結

非公平鎖與公平鎖的lock方法有以下兩點不同:

  1. 非公平鎖: 進入lock方法後直接嘗試CAS獲得鎖,成功則直接返回;
    公平鎖: 進入lock方法後首先要獲取鎖的等待狀態state,判斷是否有線程在等待鎖(state是否爲0)
  1. 非公平鎖: CAS未成功後進入父類AQS的acquire方法,然後調用自己的nonfairTryAcquire方法,如果暫時沒有線程持有鎖或者當前鎖可重入,則直接獲得鎖,也不會對阻塞隊列有沒有線程在等待鎖作判斷,如果沒有獲得鎖則後續步驟和公平鎖一樣,將當前線程封裝成結點插入到隊列中自旋等待。
    公平鎖: 在步驟1的基礎上,當鎖的等待狀態爲0,即未有線程持有鎖時,會進而判斷阻塞隊列中有沒有線程在等待鎖

對比之下,非公平鎖的吞吐量更大,但是也有可能使阻塞隊列中的線程長期處於飢餓狀態。

參考:https://javadoop.com/post/AbstractQueuedSynchronizer

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