<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java面經(jīng)之AQS原理

          共 6487字,需瀏覽 13分鐘

           ·

          2021-04-01 15:21

          點擊藍字關注我們,獲取更多面經(jīng)








          AQS(AbstractQueuedSynchronizer)即 隊列同步器,是用來構建鎖或者其他同步組件的基礎框架,它使用了一個int成員變量表示同步狀態(tài),通過內(nèi)置的FIFO隊列來完成資源獲取線程的排隊工作。


                 同步器的主要使用方式是繼承。子類推薦被定義為自定義同步組件的靜態(tài)內(nèi)部類,同步器自身沒有實現(xiàn)任何同步接口,它僅僅是定義了若干同步狀態(tài)獲取和釋放的方法來供自定義同步組件使用,同步器既可以支持獨占式地獲取同步狀態(tài),也可以支持共享式地獲取同步狀態(tài)。


                 同步器的設計是基于模板方法模式的,也就是說,使用者需要繼承同步器并重寫指定的方法,隨后將同步器組合在自定義同步組件的實現(xiàn)中,并調(diào)用同步器提供的模板方法,而這些模板方法將會調(diào)用使用者重寫的方法。重寫同步器指定的方法時,需要使用同步器提供的如下3個方法來訪問或修改同步狀態(tài)。

          ·getState():獲取當前同步狀態(tài)。

          ·setState(int newState):設置當前同步狀態(tài)。


          ·compareAndSetState(int expect,int update):使用CAS設置當前狀態(tài),該方法能夠保證狀態(tài)設置的原子性。


                 同步器提供的模板方法基本上分為3類:獨占式獲取與釋放同步狀態(tài)、共享式獲取與釋放同步狀態(tài)和查詢同步隊列中的等待線程情況。自定義同步組件將使用同步器提供的模板方法來實現(xiàn)自己的同步語義。


                 下面看下 同步器可被重寫的方法如下(如,ReentrantLock就重寫了下面的某些方法) 

          同步器可重寫的方法如圖:

          下面我們就舉個例子,看獨占鎖(獨占鎖就是在同一時刻只能有一個線程獲取到鎖,而其他獲取鎖的線程只能處于同步隊列中等待,只有獲取鎖的線程釋放了鎖,后繼的線程才能夠獲取鎖)是如何利用AQS,和重寫他的方法來達到獨占鎖的效果的。

          public class Mutex implements Lock {    // 靜態(tài)內(nèi)部類,自定義同步器    private static class Sync extends AbstractQueuedSynchronizer {        // 是否處于占用狀態(tài)        protected boolean isHeldExclusively() {            return getState() == 1;        }        // 當狀態(tài)為0的時候獲取鎖        public boolean tryAcquire(int acquires) {            if (compareAndSetState(0, 1)) {                setExclusiveOwnerThread(Thread.currentThread());                return true;            }            return false;        }        // 釋放鎖,將狀態(tài)設置為0        protected boolean tryRelease(int releases) {            if (getState() == 0) throw new                    IllegalMonitorStateException();            setExclusiveOwnerThread(null);            setState(0);            return true;        }        // 返回一個Condition,每個condition都包含了一個condition隊列        Condition newCondition() { return new ConditionObject(); }    }    // 僅需要將操作代理到Sync上即可    private final Sync sync = new Sync();    public void lock() { sync.acquire(1); }    public boolean tryLock() { return sync.tryAcquire(1); }    public void unlock() { sync.release(1); }    public Condition newCondition() { return sync.newCondition(); }    public boolean isLocked() { return sync.isHeldExclusively(); }    public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }    public void lockInterruptibly() throws InterruptedException {        sync.acquireInterruptibly(1);    }    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {        return sync.tryAcquireNanos(1, unit.toNanos(timeout));    }        }


          Mutex中定義了一個靜態(tài)內(nèi)部類,該內(nèi)部類繼承了同步器并實現(xiàn)了獨占式獲取和釋放同步狀態(tài)。在tryAcquire(int acquires)方法中,如果經(jīng)過CAS設置成功(同步狀態(tài)設置為1),則代表獲取了同步狀態(tài),而在tryRelease(int releases)方法中只是將同步狀態(tài)重置為0。用戶使用Mutex時并不會直接和內(nèi)部同步器的實現(xiàn)打交道,而是調(diào)用Mutex提供的方法,在Mutex的實現(xiàn)中,以獲

          取鎖的lock()方法為例,只需要在方法實現(xiàn)中調(diào)用同步器的模板方法acquire(int args)即可。

                下面我們隊隊列同步器AQS的實現(xiàn)進行分析。主要包括:同步隊列、獨占式同步狀態(tài)獲取與釋放、共享式同步狀態(tài)獲取與釋放以及超時獲取同步狀態(tài)等同步器的核心數(shù)據(jù)結構與模板方法。


                  同步器依賴內(nèi)部的同步隊列(一個FIFO雙向隊列)來完成同步狀態(tài)的管理,當前線程獲取同步狀態(tài)失敗時,同步器會將當前線程以及等待狀態(tài)等信息構造成為一個節(jié)點(Node)并將其加入同步隊列,同時會阻塞當前線程,當同步狀態(tài)釋放時,會把首節(jié)點中的線程喚醒,使其再次嘗試獲取同步狀態(tài)。節(jié)點(Node)用來保存獲取同步狀態(tài)失敗的線程引用、等待狀態(tài)以及前驅和后繼節(jié)點,具體的定義和狀態(tài)大家可以自行去看下源碼這里略過。同步隊列的基本結構如圖所示:


                  同步器包含了兩個節(jié)點類型的引用,一個指向頭節(jié)點,而另一個指向尾節(jié)點。試想一下,當一個線程成功地獲取了同步狀態(tài)(或者鎖),其他線程將無法獲取到同步狀態(tài),轉而被構造成為節(jié)點并加入到同步隊列中,而這個加入隊列的過程必須要保證線程安全,因此同步器提供了一個基于CAS的設置尾節(jié)點的方法:compareAndSetTail(Node expect,Nodeupdate),它需要傳遞當前線程“認為”的尾節(jié)點和當前節(jié)點,只有設置成功后,當前節(jié)點才正式與之前的尾節(jié)點建立關聯(lián)。


                  同步隊列遵循FIFO,首節(jié)點是獲取同步狀態(tài)成功的節(jié)點,首節(jié)點的線程在釋放同步狀態(tài)時,將會喚醒后繼節(jié)點,而后繼節(jié)點將會在獲取同步狀態(tài)成功時將自己設置為首節(jié)點。


                  下面分別看下獨占式同步狀態(tài)獲取與釋放和共享式同步狀態(tài)獲取與釋放是如何實現(xiàn)的。

                  1)獨占式同步狀態(tài)獲取與釋放

                 通過調(diào)用同步器的acquire(int arg)方法可以獲取同步狀態(tài),該方法對中斷不敏感,也就是由于線程獲取同步狀態(tài)失敗后進入同步隊列中,后續(xù)對線程進行中斷操作時,線程不會從同步隊列中移出,該方法代碼如下:

              上述代碼主要完成了同步狀態(tài)獲取、節(jié)點構造、加入同步隊列以及在同步隊列中自旋等待的相關工作,其主要邏輯是:首先調(diào)用自定義同步器實現(xiàn)的tryAcquire(int arg)方法,該方法保證線程安全的獲取同步狀態(tài),如果同步狀態(tài)獲取失敗,則構造同步節(jié)點并通過addWaiter(Node node)方法將該節(jié)點加入到同步隊列的尾部,最后調(diào)用acquireQueued(Node node,int arg)方法,使得該節(jié)點以“死循環(huán)”的方式獲取同步狀態(tài)。如果獲取不到則阻塞節(jié)點中的線程,而被阻塞線程的喚醒主要依靠前驅節(jié)點的出隊或阻塞線程被中斷來實現(xiàn)。


              我們看下同步器的addWaiter和enq的實現(xiàn)代碼如下:


          上述代碼通過使用compareAndSetTail(Node expect,Node update)方法來確保節(jié)點能夠被線程安全添加。


          在enq(final Node node)方法中,同步器通過“死循環(huán)”來保證節(jié)點的正確添加,在“死循環(huán)”中只有通過CAS將節(jié)點設置成為尾節(jié)點之后,當前線程才能從該方法返回,否則,當前線程不斷地嘗試設置。


           節(jié)點進入同步隊列之后,就進入了一個自旋的過程(acquireQueued方法),每個節(jié)點(或者說每個線程)都在自省地觀察,當條件滿足,獲取到了同步狀態(tài),就可以從這個自旋過程中退出,否則依舊留在這個自旋過程中(并會阻塞節(jié)點的線程),如代碼所示

          在acquireQueued(final Node node,int arg)方法中,當前線程在“死循環(huán)”中嘗試獲取同步狀態(tài),而只有前驅節(jié)點是頭節(jié)點才能夠嘗試獲取同步狀態(tài)。


          獨占式同步狀態(tài)獲取流程,也就是acquire(int arg)方法調(diào)用流程,如圖所示


          下面看下如何釋放釋放同步狀態(tài)的:


                  該方法執(zhí)行時,會喚醒頭節(jié)點的后繼節(jié)點線程,unparkSuccessor(Node node)方法使用LockSupport(這里不做介紹)來喚醒處于等待狀態(tài)的線程。


                 適當做個總結:在獲取同步狀態(tài)時,同步器維護一個同步隊列,獲取狀態(tài)失敗的線程都會被加入到隊列中并在隊列中進行自旋;移出隊列(或停止自旋)的條件是前驅節(jié)點為頭節(jié)點且成功獲取了同步狀態(tài)。在釋放同步狀態(tài)時,同步器調(diào)用tryRelease(int arg)方法釋放同步狀態(tài),然后喚醒頭節(jié)點的后繼節(jié)點。


                2)共享式同步狀態(tài)獲取與釋放

                共享式獲取與獨占式獲取最主要的區(qū)別在于同一時刻能否有多個線程同時獲取到同步狀態(tài)。


                通過調(diào)用同步器的acquireShared(int arg)方法可以共享式地獲取同步狀態(tài),該方法代碼如下:

                 在acquireShared(int arg)方法中,同步器調(diào)用tryAcquireShared(int arg)方法嘗試獲取同步狀態(tài),tryAcquireShared(int arg)方法返回值為int類型,當返回值大于等于0時,表示能夠獲取到同步狀態(tài)。因此,在共享式獲取的自旋過程中,成功獲取到同步狀態(tài)并退出自旋的條件就是tryAcquireShared(int arg)方法返回值大于等于0??梢钥吹剑赿oAcquireShared(int arg)方法的自旋過程中,如果當前節(jié)點的前驅為頭節(jié)點時,嘗試獲取同步狀態(tài),如果返回值大于等于0,表示該次獲取同步狀態(tài)成功并從自旋過程中退出.


               與獨占式一樣,共享式獲取也需要釋放同步狀態(tài),通過調(diào)用releaseShared(int arg)方法可以釋放同步狀態(tài),該方法代碼:

                  該方法在釋放同步狀態(tài)之后,將會喚醒后續(xù)處于等待狀態(tài)的節(jié)點。對于能夠支持多個線程同時訪問的并發(fā)組件(比如Semaphore),它和獨占式主要區(qū)別在于tryReleaseShared(int arg)方法必須確保同步狀態(tài)(或者資源數(shù))線程安全釋放,一般是通過循環(huán)和CAS來保證的,因為釋放同步狀態(tài)的操作會同時來自多個線程


            3)獨占式超時獲取同步狀態(tài)

                  通過調(diào)用同步器的doAcquireNanos(int arg,long nanosTimeout)方法可以超時獲取同步狀態(tài),即在指定的時間段內(nèi)獲取同步狀態(tài),如果獲取到同步狀態(tài)則返回true,否則,返回false。該方法提供了傳統(tǒng)Java同步操作(比如synchronized關鍵字)所不具備的特性。


                 超時獲取同步狀態(tài)過程可以被視作響應中斷獲取同步狀態(tài)過程的“增強版”,doAcquireNanos(int arg,long nanosTimeout)方法在支持響應中斷的基礎上,增加了超時獲取的特性。針對超時獲取,主要需要計算出需要睡眠的時間間隔nanosTimeout,為了防止過早通知,nanosTimeout計算公式為:nanosTimeout-=now-lastTime,其中now為當前喚醒時間,lastTime為上次喚醒時間,如果nanosTimeout大于0則表示超時時間未到,需要繼續(xù)睡眠nanosTimeout納秒,反之,表示已經(jīng)超時.具體代碼這里就不貼出來了。大致的流程圖如下:

                  獨占式超時獲取同步狀態(tài)doAcquireNanos(int arg,long nanosTimeout)和獨占式獲取同步狀態(tài)acquire(int args)在流程上非常相似,其主要區(qū)別在于未獲取到同步狀態(tài)時的處理邏輯。acquire(int args)在未獲取到同步狀態(tài)時,將會使當前線程一直處于等待狀態(tài),而doAcquireNanos(int arg,long nanosTimeout)會使當前線程等待nanosTimeout納秒,如果當前線程在nanosTimeout納秒內(nèi)沒有獲取到同步狀態(tài),將會從等待邏輯中自動返回。









          更多面經(jīng)





          360-測試開發(fā)面經(jīng)(一)


          百度-測試開發(fā)面經(jīng)(一)


          字節(jié)跳動-測試開發(fā)面經(jīng)(一)


              掃描二維碼

             獲取更多面經(jīng)

            扶搖就業(yè)  


          瀏覽 107
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  爱搞逼综合网 | 久久网一区二区 | 在线内射视频 | 色哟哟哟 入口国产精品 | 国产精品一级女 |