天天看點

AQS底層原理

前言

對于安卓開發人員來說,AQS也是一個非常重要的知識點。如果把下面的知識記住了,那麼面試應該問題不大了。

一.模闆設計模式

我們平時感覺不到AQS(全程是​

​AbstractQueuedSynchronizer​

​)的存在,是因為AQS在使用的時候,一般是同步工具類的内部類繼承AQS,然後再在其他方法裡面封裝對這個内部類的操作。在學習AQS之前,我們必須要了解它的設計模式——模闆模式

關于模闆設計模式,我之前有過專門的講解。​​​大家可以看我之前的一篇部落格​​

二.AQS

前言:關于Lock

這裡有一篇​​介紹Lock的文章​​,我推薦大家看一下。

我的了解就是:​

​Lock​

​​是​

​synchronized​

​​的改進版。這個觀點在​​某一篇部落格​​中有介紹,下面我把關于這部分的介紹截圖發出來。

AQS底層原理

1.自定義獨占鎖(不可重入)

我們可以使用AQS自定義一個獨占鎖。首先,這個獨占鎖一定實作​

​Lock​

​​接口。同時要實作​

​Lock​

​裡面的這些方法

AQS底層原理

然後我們定義内部類并繼承AQS,

private static class Sync extends AbstractQueuedSynchronizer {
        /*判斷處于占用狀态*/
        @Override
        protected boolean isHeldExclusively() {
            return getState()==1;
        }

        /*獲得鎖*/
        @Override
        protected boolean tryAcquire(int arg) {
            //CAS操作
            if(compareAndSetState(0,1)){
                //儲存獲得鎖的線程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /*釋放鎖*/
        @Override
        protected boolean tryRelease(int arg) {
            if(getState()==0){
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            //compareAndSetState(1,0);
            return true;
        }

        // 傳回一個Condition,每個condition都包含了一個condition隊列
        Condition newCondition() {
            return new ConditionObject();
        }
    }      

然後實作​

​Lock​

​所需要的方法

// 僅需要将操作代理到Sync上即可
    private final Sync sync = new Sync();

    public void lock() {
        System.out.println(Thread.currentThread().getName()+" ready get lock");
        sync.acquire(1);
        System.out.println(Thread.currentThread().getName()+" already got lock");
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    public void unlock() {
        System.out.println(Thread.currentThread().getName()+" ready release lock");
        sync.release(1);
        System.out.println(Thread.currentThread().getName()+" already released lock");
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }      

我們會發現所有的操作其實都是對​

​Sync​

​類的操作。

其中有一個小問題

就是獲得鎖的方法​

​lock​

​​與解開鎖的方法​

​unlock​

​​都是調用的​

​Sync​

​​的​

​acquire​

​​和​

​release​

​​。但是我們在Sync 方法中并沒有找到這兩個方法。是以我們追蹤一下​

​acquire​

​方法看看

AQS底層原理

我們發現它調用了AQS的​

​tryAcquire​

​​方法,而這個方法就是我們在内部類重寫的。這就合理了!​

​release​

​方法也是同理

完整代碼

public class SelfLock implements Lock {
    // 靜态内部類,自定義同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        /*判斷處于占用狀态*/
        @Override
        protected boolean isHeldExclusively() {
            return getState()==1;
        }

        /*獲得鎖*/
        @Override
        protected boolean tryAcquire(int arg) {
            //CAS操作
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /*釋放鎖*/
        @Override
        protected boolean tryRelease(int arg) {
            if(getState()==0){
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            //compareAndSetState(1,0);
            return true;
        }

        // 傳回一個Condition,每個condition都包含了一個condition隊列
        Condition newCondition() {
            return new ConditionObject();
        }
    }
    // 僅需要将操作代理到Sync上即可
    private final Sync sync = new Sync();

    public void lock() {
        System.out.println(Thread.currentThread().getName()+" ready get lock");
        sync.acquire(1);
        System.out.println(Thread.currentThread().getName()+" already got lock");
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    public void unlock() {
        System.out.println(Thread.currentThread().getName()+" ready release lock");
        sync.release(1);
        System.out.println(Thread.currentThread().getName()+" already released lock");
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}      

2.可重入鎖ReentrantLock

我們剛剛的自定義鎖其實原理已經和​

​ReentrantLock​

​​很相似了。但是還是有一個很大的差別,就是可重入性。前面我們自定義的那個是不可重入的,而​

​ReentrantLock​

​是可重入的。

①什麼是可重入呢?

說一個應用場景吧,比如遞歸。在一個方法中,有類似這樣的邏輯

AQS底層原理

而在業務邏輯代碼中 會進行遞歸,也就是重新調用。這時候問題來了。我們之前是鎖住了的,是以在遞歸第一次的時候,會發現鎖已經被占用了(其實是被自己這個線程占用的)。是以對于可重入鎖來說,會判斷一下占用此鎖的線程是不是就是自己,如果是的話,就皆大歡喜,将某一個辨別符+1表示自己又占了一次鎖,然後就可以繼續操作。如果不是自己占用的就進入等待隊列,等待鎖被釋放。

是以在這裡可重入鎖就是你可以對一個​

​ReentrantLock​

​對象多次執行​

​lock()​

​加鎖和​

​unlock()​

​釋放鎖,也就是可以對一個鎖加多次,叫做可重入加鎖。

②state

到這裡可重入性大家就了解了。那麼還有一個問題,就是剛剛說到的辨別符。其實就是AQS裡面一個非常非常重要的成員變量 ​

​state​

​。

AQS底層原理

當沒有線程占用鎖的時候,​

​state​

​​為0.當有線程占用的時候,​

​state≥1​

​​.當某一個線程隻占用了一次的時候,​

​state​

​​為1,當某一個線程重入了多次的時候,​

​state>1​

​.

③那麼具體如何來實作可重入性呢?别急,往下看

我們把剛剛的例子改為可重入的,隻需要更改内部類就可以

// 靜态内部類,自定義同步器
private static class Sync extends AbstractQueuedSynchronizer {
   //*********************
     // 不同點1:是否處于占用狀态
     //*********************
     protected boolean isHeldExclusively() {
         return getState() > 0;
     }

     // 當狀态為0的時候擷取鎖
     public boolean tryAcquire(int acquires) {
         if (compareAndSetState(0, 1)) {
             setExclusiveOwnerThread(Thread.currentThread());
             return true;
         }else if(getExclusiveOwnerThread()==Thread.currentThread()){
         //*********************
         // 不同點2:如果是本線程占用了鎖,那麼将再占用一次,并且state+1
         //*********************
             setState(getState()+1);
             return  true;
         }
         return false;
     }

     // 釋放鎖,将狀态設定為0
     protected boolean tryRelease(int releases) {
       //*********************
       // 小不同點:異常判斷
       //*********************
         if(getExclusiveOwnerThread()!=Thread.currentThread()){
             throw new IllegalMonitorStateException();
         }
         if (getState() == 0)
             throw new IllegalMonitorStateException();
     //*********************
       // 不同點3:釋放的時候,每一次釋放都将state-1,當減到0時說明鎖已經沒有被占用了,則設定占用鎖的線程為null
       //*********************
         setState(getState()-1);
         if(getState()==0){
             setExclusiveOwnerThread(null);
         }
         return true;
     }

     // 傳回一個Condition,每個condition都包含了一個condition隊列
     Condition newCondition() {
         return new ConditionObject();
     }
 }      

3.AQS核心思想之CLH隊列原理

CLH隊列由連結清單實作,每一個線程被封裝成一個Node結點。除此之外還有兩個屬性,即​

​myPred​

​和​

​locked​

​。

AQS底層原理

​myPred​

​​指向每一個結點的前驅節點,​

​locked​

​​為​

​true​

​​表明這個節點還需要鎖,一旦​

​locked​

​​變為​

​false​

​,則說明這個節點已經使用完鎖并且将其釋放掉了。

那麼具體怎麼看前面的節點的線程已經釋放掉鎖了呢?就是通過不斷自旋的方式,即某一線程在前驅結點的​

​locked​

​字段上旋轉

AQS底層原理

比如​

​QNode_A​

​​的​

​locked​

​​由​

​true​

​​變成了​

​false​

​​,那麼​

​QNode_B​

​​的​

​myPred​

​​在某一次自旋後發現了這個變化,則說明前面線程已經使用完鎖了,該輪到我​

​QNode_B​

​節點來用鎖了。

這就是CLH隊列的基本思想。AQS就是基于這個思想來的,當然,在具體實作方面對CLH做了一些改進。比如使用雙向連結清單,而且不會無限次自旋,而是詢問一定次數後就将線程進行阻塞,當鎖被釋放的時候就會喚醒阻塞的線程。雖說如此,但核心思想還是與CLH保持一緻的。

​sunchronized​

​實作的基本思想也是CLH隊列鎖。

公平鎖與非公平鎖

以上也是一個公平鎖的概念。就是每一個來的線程都得去排隊,誰也不能例外。但是還有一種是非公平鎖,顧名思義就是每一個新來的線程可以不用排隊直接搶占鎖。

其實公平鎖和非公平鎖的實作是非常相似的

AQS底層原理

唯一的一處不同是在​

​tryAcquire​

​這個方法

先看公平鎖

/**
 * Acquires only if thread is first waiter or empty
 */
protected final boolean tryAcquire(int acquires) {
    if (getState() == 0 && !hasQueuedPredecessors() &&
        compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}      

再看非公平鎖

/**
 * Acquire for non-reentrant cases after initialTryLock prescreen
 */
protected final boolean tryAcquire(int acquires) {
    if (getState() == 0 && compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}      

三.總結