EventBus系列『番外』——認(rèn)真剖析 『PendingPostQueue』隊(duì)列的實(shí)現(xiàn)思想

我們把注冊(cè)、注銷堕绩、post和postSticky事件收發(fā)都已經(jīng)剖析完畢,接下來(lái)我們講一下EventBus隊(duì)列的實(shí)現(xiàn)思想.
我的其他文章地址,歡迎品讀:

EventBus系列『一』——注冊(cè)與注銷

EventBus系列『二』——Post與postSticky事件的發(fā)布與接收

EventBus系列『番外』——認(rèn)真剖析 『PendingPostQueue』隊(duì)列的實(shí)現(xiàn)思想

概念

什么是隊(duì)列

隊(duì)列是一種數(shù)據(jù)結(jié)構(gòu),它支持 FIFO榕茧,尾部添加、頭部刪除(先進(jìn)隊(duì)列的元素先出隊(duì)列

Java中的常用隊(duì)列

在Java中提供了一個(gè) BlockingQueue<E>接口,通過(guò)實(shí)現(xiàn)這個(gè)接口來(lái)實(shí)現(xiàn)隊(duì)列存儲(chǔ)客给,我們常用的隊(duì)列有

知識(shí)流程鋪墊

我們先回想一下將事件放入隊(duì)列的過(guò)程桩引,以threadMode = MAIN為例:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            ...
            case MAIN:
                if (isMainThread) {
                    //直接反射執(zhí)行
                    invokeSubscriber(subscription, event);
                } else {
                    //入列操作
                    mainThreadPoster.enqueue(subscription, event);  
                }
                break;
         ...
}

進(jìn)入HandlerPoster.java缎讼,執(zhí)行enqueue 函數(shù),將Event放入隊(duì)列中

public void enqueue(Subscription subscription, Object event) {
       //將完整Event事件封裝成 PendingPost 類型
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            //入列
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

PendingPostQueue 隊(duì)列的實(shí)現(xiàn)

PendingPostQueue隊(duì)列的實(shí)現(xiàn)不同于上述兩個(gè)隊(duì)列坑匠,它的內(nèi)部既 沒有維護(hù) 數(shù)組 血崭,也沒有維護(hù)Node節(jié)點(diǎn),同時(shí)也沒有維護(hù) List鏈表,那么它是如何實(shí)現(xiàn)的呢?
答案是:內(nèi)存指針

我們先了解它內(nèi)部維護(hù)的變量都有哪些 :

final class PendingPostQueue {
    //頭部
    private PendingPost head; 
   //尾部
    private PendingPost tail;
}

通過(guò)源碼我們可以看到PendingPostQueue隊(duì)列的頭部和尾部都被包裝成了PendingPost類型,我們來(lái)看看PendingPost類的實(shí)現(xiàn):

final class PendingPost {
    private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();

    Object event;
    Subscription subscription;
    //內(nèi)部變量夹纫,由于存儲(chǔ)下一個(gè)PendingPost對(duì)象
    PendingPost next; 

    private PendingPost(Object event, Subscription subscription) {
        this.event = event;
        this.subscription = subscription;
    }
    //@重點(diǎn):在將Event事件放入隊(duì)列之前咽瓷,我們會(huì)執(zhí)行此方法。
    //將完整的Event事件包裝成PendingPost對(duì)象
    static PendingPost obtainPendingPost(Subscription subscription, Object event) {
        synchronized (pendingPostPool) {
            int size = pendingPostPool.size();
            //判定pendingPostPool鏈表大小
            if (size > 0) {
                //獲取并移除鏈表中的最后一個(gè)元素
                PendingPost pendingPost = pendingPostPool.remove(size - 1);
                //重新為最后一個(gè)元素賦值
                pendingPost.event = event;
                pendingPost.subscription = subscription;
                pendingPost.next = null;
                //返回一個(gè)包裝好的 PendingPost 對(duì)象
                return pendingPost;
            }
        }
       //new 一個(gè)PendingPost對(duì)象 進(jìn)行包裝
        return new PendingPost(event, subscription);
    }
 //將 pendingPost元素保存至鏈表舰讹,鏈表最大不超過(guò) 10000
 static void releasePendingPost(PendingPost pendingPost) {
        pendingPost.event = null;
        pendingPost.subscription = null;
        pendingPost.next = null;
        synchronized (pendingPostPool) {
            // Don't let the pool grow indefinitely
            if (pendingPostPool.size() < 10000) {
                pendingPostPool.add(pendingPost);
            }
        }
    }
}

我們?cè)?知識(shí)流程鋪墊 可以看到放入隊(duì)列之前先把完整Event事件封裝成 PendingPost, 即僅執(zhí)行 obtainPendingPost(Subscription subscription, Object event)函數(shù)忱详。

回到 PendingPostQueue.java ,我們看看他是如何實(shí)現(xiàn)入棧和出棧的

我們看看是如何通過(guò)內(nèi)存指針,指向下一個(gè)元素的內(nèi)存位置跺涤,獲取元素值的

進(jìn)棧流程
final class PendingPostQueue {
    private PendingPost head;
    private PendingPost tail;

    synchronized void enqueue(PendingPost pendingPost) {
         //判定pendingPost是否為空
        if (pendingPost == null) {
            throw new NullPointerException("null cannot be enqueued");
        }
       //尾部不為空匈睁,證明隊(duì)列中已存在其他元素
        if (tail != null) {
            tail.next = pendingPost; //為tail對(duì)象的 next屬性賦值
            tail = pendingPost;  //重新為tail對(duì)象賦值
        } else if (head == null) { //頭部為空,證明隊(duì)列是空的桶错,進(jìn)入元素是第一個(gè)
            head = tail = pendingPost; //同時(shí)賦值給頭尾 
        } else {
            throw new IllegalStateException("Head present, but no tail");
        }
        notifyAll();
    }
}
    1. 入棧時(shí)先進(jìn)行判Null,以免Null進(jìn)入堆棧.
    1. 若是堆棧首次插入元素 A航唆,此時(shí)的 headtail 都應(yīng)該為Null,將需要插入元素A同時(shí)賦給 headtail, 那么此時(shí) headtail的內(nèi)存指針都是指向 A
  • 3.若我們?cè)俅螆?zhí)行插入元素 B院刁,此時(shí)的headtail都是有值的糯钙,那么我們將執(zhí)行 tail.next = B相當(dāng)于將元素 B賦予了AA.next屬性值,那么此時(shí) head.next的值也就等于B退腥;緊接著我們重新為tail賦新值 B,那么此時(shí) A.nexthead.next 就等于 tail .
  • 4.我們就如此一直插入數(shù)據(jù)任岸,tail永遠(yuǎn)保存最后一個(gè)值,而通過(guò)不斷遍歷head.next我們就可以難道堆棧里所有的值.實(shí)現(xiàn)了FIFO(先進(jìn)先出)排序原則
出棧流程
final class PendingPostQueue {
    private PendingPost head;
    private PendingPost tail;
     //出棧
    synchronized PendingPost poll() {
        PendingPost pendingPost = head; //獲取頭部數(shù)據(jù)
        if (head != null) {  //若head不為空狡刘,證明隊(duì)列中還存在數(shù)據(jù)
            head = head.next; //獲取下一個(gè)數(shù)據(jù)元素,賦予 head
            if (head == null) { //若下一個(gè)數(shù)據(jù)元素為空,證明隊(duì)列已無(wú)元素
                tail = null; //將尾部設(shè)置為Null
            }
        }
        return pendingPost; //返回pendingPost對(duì)象
    }
    //出棧,指定最長(zhǎng)等待時(shí)間
    synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
        if (head == null) {
            wait(maxMillisToWait); //等待時(shí)長(zhǎng)
        }
        return poll();
    }

}

第一次執(zhí)行poll函數(shù)享潜,獲取頭部的pendingPost元素,然后進(jìn)行判斷head值是否為空,若不為Null,獲取head.next值作為下一次的執(zhí)行poll函數(shù)head值嗅蔬,一次類推直到head.nextNull時(shí),說(shuō)明沒有了下一元素剑按,也就說(shuō)到 棧底 了,將tailNull澜术,結(jié)束艺蝴。

簡(jiǎn)單實(shí)例

為了方便了解,我做了一個(gè)依照EventBus的結(jié)構(gòu)我做了一個(gè)Demo小樣鸟废,供大家參考:

Entry.java

public class Entry {
    private String flag;
    Entry next;

    public Entry(String flag) {
        this.flag = flag;
    }

    public static Entry createEntry(String flag) {
        return new Entry(flag);
    }
}

EntryControl.java

public class EntryControl {
    private Entry head;
    private Entry tail;

    synchronized void enqueue(Entry entry) {
        if (tail != null) {
            tail.next = entry;
            tail = entry;
        } else if (head == null) {
            head = tail = entry;
        } else {
            throw new IllegalStateException("Head present, but no tail");
        }
        System.out.println("Head HashCode:" + head.hashCode());
        System.out.println("Tail HashCode:" + tail.hashCode());
        notifyAll();
    }

    synchronized Entry poll() {
        Entry entry = head;
        if (head != null) {
            head = head.next;
            if (head == null) {
                tail = null;
            }
            System.out.println("Current Head HashCode:" + entry.hashCode());
        }
        return entry;
    }
}

Test.java 執(zhí)行程序

public class Test {
    public static void main(String[] args) {
        EntryControl entryControl = new EntryControl();

        for (int index = 0; index < 5; index++) {
            Entry entry = Entry.createEntry("我是第" + index);
            entryControl.enqueue(entry);
        }
        System.out.println("存值完成===========================");
        while (true) {
            Entry pendingPost = entryControl.poll();
            if (pendingPost == null) {
                return;
            }
//            System.out.println().e(TAG,"我獲取的記過(guò)" + pendingPost.subscription + "," + pendingPost.event);
        }

    }
}

運(yùn)行結(jié)果

Head HashCode:356573597
Tail HashCode:356573597
Head HashCode:356573597
Tail HashCode:1735600054
Head HashCode:356573597
Tail HashCode:21685669
Head HashCode:356573597
Tail HashCode:2133927002
Head HashCode:356573597
Tail HashCode:1836019240
存值完成===========================
Current Head HashCode:356573597
Current Head HashCode:1735600054
Current Head HashCode:21685669
Current Head HashCode:2133927002
Current Head HashCode:1836019240

如此就清晰明了了.

This ALL! Thanks EveryBody!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猜敢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盒延,更是在濱河造成了極大的恐慌缩擂,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兰英,死亡現(xiàn)場(chǎng)離奇詭異撇叁,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)畦贸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門陨闹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)楞捂,“玉大人,你說(shuō)我怎么就攤上這事趋厉≌郑” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵君账,是天一觀的道長(zhǎng)繁堡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)乡数,這世上最難降的妖魔是什么椭蹄? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮净赴,結(jié)果婚禮上绳矩,老公的妹妹穿的比我還像新娘。我一直安慰自己玖翅,他們只是感情好翼馆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著金度,像睡著了一般应媚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猜极,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天中姜,我揣著相機(jī)與錄音,去河邊找鬼魔吐。 笑死扎筒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的酬姆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼奥溺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼辞色!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起浮定,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤相满,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后桦卒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體立美,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年方灾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了建蹄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碌更。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖洞慎,靈堂內(nèi)的尸體忽然破棺而出痛单,到底是詐尸還是另有隱情,我是刑警寧澤劲腿,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布旭绒,位于F島的核電站,受9級(jí)特大地震影響焦人,放射性物質(zhì)發(fā)生泄漏挥吵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一花椭、第九天 我趴在偏房一處隱蔽的房頂上張望忽匈。 院中可真熱鬧,春花似錦个从、人聲如沸脉幢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嫌松。三九已至,卻和暖如春奕污,著一層夾襖步出監(jiān)牢的瞬間萎羔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工碳默, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贾陷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓嘱根,卻偏偏與公主長(zhǎng)得像髓废,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子该抒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容