EventBus系列『番外』——认真剖析 『PendingPostQueue』队列的实现思想

我们把注册、注销、post和postSticky事件收发都已经剖析完毕,接下来我们讲一下EventBus队列的实现思想.
我的其他文章地址,欢迎品读:

EventBus系列『一』——注册与注销

EventBus系列『二』——Post与postSticky事件的发布与接收

EventBus系列『番外』——认真剖析 『PendingPostQueue』队列的实现思想

概念

什么是队列

队列是一种数据结构,它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列

Java中的常用队列

在Java中提供了一个 BlockingQueue<E>接口,通过实现这个接口来实现队列存储,我们常用的队列有

知识流程铺垫

我们先回想一下将事件放入队列的过程,以threadMode = MAIN为例:

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

进入HandlerPoster.java,执行enqueue 函数,将Event放入队列中

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 队列的实现

PendingPostQueue队列的实现不同于上述两个队列,它的内部既 没有维护 数组 ,也没有维护Node节点,同时也没有维护 List链表,那么它是如何实现的呢?
答案是:内存指针

我们先了解它内部维护的变量都有哪些 :

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

通过源码我们可以看到PendingPostQueue队列的头部和尾部都被包装成了PendingPost类型,我们来看看PendingPost类的实现:

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

    Object event;
    Subscription subscription;
    //内部变量,由于存储下一个PendingPost对象
    PendingPost next; 

    private PendingPost(Object event, Subscription subscription) {
        this.event = event;
        this.subscription = subscription;
    }
    //@重点:在将Event事件放入队列之前,我们会执行此方法。
    //将完整的Event事件包装成PendingPost对象
    static PendingPost obtainPendingPost(Subscription subscription, Object event) {
        synchronized (pendingPostPool) {
            int size = pendingPostPool.size();
            //判定pendingPostPool链表大小
            if (size > 0) {
                //获取并移除链表中的最后一个元素
                PendingPost pendingPost = pendingPostPool.remove(size - 1);
                //重新为最后一个元素赋值
                pendingPost.event = event;
                pendingPost.subscription = subscription;
                pendingPost.next = null;
                //返回一个包装好的 PendingPost 对象
                return pendingPost;
            }
        }
       //new 一个PendingPost对象 进行包装
        return new PendingPost(event, subscription);
    }
 //将 pendingPost元素保存至链表,链表最大不超过 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);
            }
        }
    }
}

我们在 知识流程铺垫 可以看到放入队列之前先把完整Event事件封装成 PendingPost, 即仅执行 obtainPendingPost(Subscription subscription, Object event)函数。

回到 PendingPostQueue.java ,我们看看他是如何实现入栈和出栈的

我们看看是如何通过内存指针,指向下一个元素的内存位置,获取元素值的

进栈流程
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");
        }
       //尾部不为空,证明队列中已存在其他元素
        if (tail != null) {
            tail.next = pendingPost; //为tail对象的 next属性赋值
            tail = pendingPost;  //重新为tail对象赋值
        } else if (head == null) { //头部为空,证明队列是空的,进入元素是第一个
            head = tail = pendingPost; //同时赋值给头尾 
        } else {
            throw new IllegalStateException("Head present, but no tail");
        }
        notifyAll();
    }
}
    1. 入栈时先进行判Null,以免Null进入堆栈.
    1. 若是堆栈首次插入元素 A,此时的 headtail 都应该为Null,将需要插入元素A同时赋给 headtail, 那么此时 headtail的内存指针都是指向 A
  • 3.若我们再次执行插入元素 B,此时的headtail都是有值的,那么我们将执行 tail.next = B相当于将元素 B赋予了AA.next属性值,那么此时 head.next的值也就等于B;紧接着我们重新为tail赋新值 B,那么此时 A.nexthead.next 就等于 tail .
  • 4.我们就如此一直插入数据,tail永远保存最后一个值,而通过不断遍历head.next我们就可以难道堆栈里所有的值.实现了FIFO(先进先出)排序原则
出栈流程
final class PendingPostQueue {
    private PendingPost head;
    private PendingPost tail;
     //出栈
    synchronized PendingPost poll() {
        PendingPost pendingPost = head; //获取头部数据
        if (head != null) {  //若head不为空,证明队列中还存在数据
            head = head.next; //获取下一个数据元素,赋予 head
            if (head == null) { //若下一个数据元素为空,证明队列已无元素
                tail = null; //将尾部设置为Null
            }
        }
        return pendingPost; //返回pendingPost对象
    }
    //出栈,指定最长等待时间
    synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
        if (head == null) {
            wait(maxMillisToWait); //等待时长
        }
        return poll();
    }

}

第一次执行poll函数,获取头部的pendingPost元素,然后进行判断head值是否为空,若不为Null,获取head.next值作为下一次的执行poll函数head值,一次类推直到head.nextNull时,说明没有了下一元素,也就说到 栈底 了,将tailNull,结束。

简单实例

为了方便了解,我做了一个依照EventBus的结构我做了一个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 执行程序

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,"我获取的记过" + pendingPost.subscription + "," + pendingPost.event);
        }

    }
}

运行结果

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!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容