【Guava學(xué)習(xí)】EventBus

1蟹演,簡單使用

首先引入guave的依賴;

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.6-jre</version>
</dependency>

再來看一下基礎(chǔ)的使用:

EventBus eventBus = new EventBus();
eventBus.register(new Object() {
    @Subscribe
    public void hehe(String name) {
        System.out.println(name);
    }

    @Subscribe
    public void haha(Object object) {
        System.out.println(object);
    }
});
eventBus.post("huang");//輸出兩遍huang臭墨;
eventBus.post(17);//輸出一遍17;
  • 首先需要一個(gè)總線EventBus膘盖,直接new出來;
  • 然后需要一個(gè)觀察者尤误,可以理解為監(jiān)聽器侠畔,最簡潔的方式是直接new一個(gè)Object,然后向其中新增方法损晤;
  • 然后需要將觀察者注冊到總線中软棺;
  • 最后發(fā)布事件;

注意點(diǎn):

  • 使用@Subscribe注解來標(biāo)注方法尤勋,該方法的返回值不會被處理喘落,所以有無返回值都是一樣的茵宪,方法的參數(shù)類型是接受的事件類型,所以方法有且只能有一個(gè)參數(shù)瘦棋;
  • 當(dāng)多個(gè)標(biāo)注了@Subscribe的方法的參數(shù)存在父子關(guān)系的時(shí)候稀火,當(dāng)發(fā)布子類型的事件時(shí),父類型的方法也將被執(zhí)行赌朋,這就是上面代碼輸出兩遍huang的原因凰狞;
  • 如果方法只標(biāo)注了@Subscribe,那么該方法的執(zhí)行是同步的沛慢,即使是多線程發(fā)布同一事件赡若,那多個(gè)線程之間存在互斥鎖,同一時(shí)間點(diǎn)团甲,只能有一個(gè)或零個(gè)執(zhí)行該方法逾冬,如下面的代碼所示;
EventBus eventBus = new EventBus();
eventBus.register(new Object() {
    @Subscribe
    public void hehe(Integer num) throws InterruptedException {
        System.out.println(num + ":" + System.currentTimeMillis());
        Thread.currentThread().sleep(100);
    }
});
for (int i = 0; i < 10; i++) {
    int finalI = i;
    new Thread(new Runnable() {
        @Override
        public void run() {
            eventBus.post(finalI);
        }
    }).start();
}
//4:1517538494324
//1:1517538494425
//8:1517538494529
//0:1517538494632
//5:1517538494735
//2:1517538494838
//9:1517538494942
//6:1517538495043
//3:1517538495147
//7:1517538495250

2躺苦,關(guān)于EventBus的并行

線程安全問題是每個(gè)java程序猿都應(yīng)該時(shí)刻注意的身腻,當(dāng)帶有@Subscribe的方法被多個(gè)方法同時(shí)執(zhí)行,且該方法內(nèi)部邏輯涉及到更改成員變量時(shí)圾另,就會出現(xiàn)線程安全問題雨涛,在默認(rèn)情況下,如果只有@Subscribe注解時(shí)蛔钙,方法時(shí)是異步執(zhí)行的笨农,即使多個(gè)線程同時(shí)調(diào)用,也需要競爭方法的同步鎖扰路,然后依次執(zhí)行尤溜;

當(dāng)需要@Subscribe標(biāo)注的方法能被多個(gè)線程同時(shí)調(diào)用,需要配合@AllowConcurrentEvents注解使用汗唱,該注解表示允許并行執(zhí)行該方法宫莱,當(dāng)有多個(gè)線程同時(shí)調(diào)用方法時(shí),因?yàn)榉椒o鎖哩罪,所以線程可以同時(shí)進(jìn)入執(zhí)行授霸,有鎖和無鎖,取決于@AllowConcurrentEvents际插,當(dāng)沒有該注解時(shí)碘耳,EventBus在生成Subscriber時(shí),使用了SynchronizedSubscriber框弛,該類型在真實(shí)調(diào)用帶有@Subscribe方法時(shí)辛辨,使用了同步鎖,具體后面講解;

EventBus eventBus = new EventBus();
eventBus.register(new Object() {
    @Subscribe
    @AllowConcurrentEvents
    public void hehe(Integer num) throws InterruptedException {
        System.out.println(num + ":" + System.currentTimeMillis());
        Thread.currentThread().sleep(100);
    }
});
for (int i = 0; i < 10; i++) {
    int finalI = i;
    new Thread(new Runnable() {
        @Override
        public void run() {
            eventBus.post(finalI);
        }
    }).start();
}
//7:1517543101034
//9:1517543101034
//1:1517543101034
//4:1517543101034
//5:1517543101034
//8:1517543101034
//0:1517543101034
//6:1517543101034
//2:1517543101034
//3:1517543101034

關(guān)于多線程調(diào)用:

  • 當(dāng)直接new一個(gè)EventBus時(shí)斗搞,當(dāng)使用post方法發(fā)布一個(gè)事件是指攒,那么調(diào)用帶有@Subscribe的方法的線程是當(dāng)前線程,在哪個(gè)線程中調(diào)用僻焚,就在哪個(gè)線程中執(zhí)行允悦;
  • 當(dāng)需要在多線程中執(zhí)行同一個(gè)標(biāo)注了@Subscribe的方法時(shí),需要使用AsyncEventBus類溅呢,并指定一個(gè)Executor線程池澡屡,那么發(fā)布事件時(shí),調(diào)用的方法都將在指定的線程池中執(zhí)行咐旧;
  • 即使指定了線程池驶鹉,如果沒有使用@AllowConcurrentEvents,那么即使調(diào)用方法(同一方法)的線程不一樣铣墨,因?yàn)橥芥i的存在室埋,執(zhí)行的時(shí)機(jī)還是依次執(zhí)行,多個(gè)線程并不會同時(shí)執(zhí)行同一方法伊约;
  • 只有指定了線程池姚淆,并且使用了@AllowConcurrentEvents注解,才能實(shí)現(xiàn)在多個(gè)線程中同時(shí)調(diào)用某個(gè)標(biāo)注了@Subscribe的方法屡律,這時(shí)需要格外注意線程并發(fā)導(dǎo)致的線程安全問題腌逢;
EventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(3));
eventBus.register(new Object() {
    @Subscribe
    @AllowConcurrentEvents
    public void hehe(Integer num) throws InterruptedException {
        Thread.currentThread().sleep(100);
        System.out.println(Thread.currentThread().getName() + "-" + num + "-" + System.currentTimeMillis());
    }
});
for (int i = 0; i < 9; i++) {
    eventBus.post(i);
}
//pool-1-thread-2-1-1517546343599
//pool-1-thread-1-0-1517546343599
//pool-1-thread-3-2-1517546343599
//pool-1-thread-2-3-1517546343704
//pool-1-thread-3-5-1517546343704
//pool-1-thread-1-4-1517546343704
//pool-1-thread-3-7-1517546343807
//pool-1-thread-1-8-1517546343807
//pool-1-thread-2-6-1517546343807

3,源碼解析

基本組件:

  • Executor:執(zhí)行的線程池超埋,默認(rèn)是DirectExecutor搏讶,他未開啟新的線程,而是在當(dāng)前線程中直接執(zhí)行霍殴;
  • SubscriberRegistry:Subscriber注冊器媒惕,每個(gè)帶有@Subscribe的方法會被注冊到該類中;
  • Dispatcher:調(diào)度器来庭,負(fù)責(zé)將事件妒蔚,分發(fā)給事件對應(yīng)的Subscriber,并使用Executor執(zhí)行這些Subscriber月弛;
  • SubscriberExceptionHandler:異常處理器肴盏,用來處理異常;

3.1帽衙,Executor

private enum DirectExecutor implements Executor {
    INSTANCE;
    
    @Override
    public void execute(Runnable command) {
      command.run();
    }
    
    @Override
    public String toString() {
      return "MoreExecutors.directExecutor()";
    }
}
  • 默認(rèn)的執(zhí)行器叁鉴,直接實(shí)現(xiàn)了Executor接口,然后覆寫方法佛寿,因?yàn)闆]有開啟新的線程,所以默認(rèn)是在當(dāng)前線程中執(zhí)行;
  • 如果是在主線程中調(diào)用冀泻,那么DirectExecutor對應(yīng)的線程就是主線程常侣,如果在其他線程中執(zhí)行,那么DirectExecutor對應(yīng)的就是其他線程弹渔,總之胳施,就是調(diào)用線程;

3.2肢专,Dispatcher

private static final class PerThreadQueuedDispatcher extends Dispatcher {
    private final ThreadLocal<Queue<Event>> queue =
            new ThreadLocal<Queue<Event>>() {
                @Override
                protected Queue<Event> initialValue() {
                    return Queues.newArrayDeque();
                }
            };

    private final ThreadLocal<Boolean> dispatching =
            new ThreadLocal<Boolean>() {
                @Override
                protected Boolean initialValue() {
                    return false;
                }
            };

    @Override
    void dispatch(Object event, Iterator<Subscriber> subscribers) {
        checkNotNull(event);
        checkNotNull(subscribers);
        Queue<Event> queueForThread = queue.get();
        queueForThread.offer(new Event(event, subscribers));

        if (!dispatching.get()) {
            dispatching.set(true);
            try 
                Event nextEvent;
                while ((nextEvent = queueForThread.poll()) != null) {
                    while (nextEvent.subscribers.hasNext()) {
                        nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
                    }
                }
            } finally {
                dispatching.remove();
                queue.remove();
            }
        }
    }
}
  • 該調(diào)度器名為單線程調(diào)度器舞肆,當(dāng)某個(gè)線程調(diào)用dispatch方法時(shí),首先從當(dāng)前線程中獲取queue博杖,這是一個(gè)ThreadLocal類型的Event隊(duì)列椿胯,然后將當(dāng)前事件放置進(jìn)去;
  • 然后根據(jù)同樣是ThreadLocal類型的dispatching字段剃根,判斷是否正在調(diào)用中哩盲,如果不是,則開始執(zhí)行具體的調(diào)度任務(wù)狈醉;
    • 但是此處的if判斷我不是很理解廉油,因?yàn)榻Y(jié)果必然一定成立;
    • 如果是單個(gè)線程苗傅,if條件中的邏輯沒有執(zhí)行完時(shí)抒线,是不可能再次調(diào)用dispatch方法的;
    • 如果是多線程調(diào)用渣慕,每個(gè)線程拿到的dispatching都是不同的嘶炭,相互之間不存在干擾,所以這個(gè)if條件是必然成立的摇庙,望大神排異解惑旱物;

下面的代碼是具體的調(diào)用邏輯,使用的是Executor進(jìn)行具體調(diào)用卫袒,并執(zhí)行方法:

final void dispatchEvent(final Object event) {
    executor.execute(
            new Runnable() {
                @Override
                public void run() {
                    try {
                        invokeSubscriberMethod(event);
                    } catch (InvocationTargetException e) {
                        bus.handleSubscriberException(e.getCause(), context(event));
                    }
                }
            });
}

@VisibleForTesting
void invokeSubscriberMethod(Object event) throws InvocationTargetException {
    try {
        method.invoke(target, checkNotNull(event));
    } catch (IllegalArgumentException e) {
        throw new Error("Method rejected target/argument: " + event, e);
    } catch (IllegalAccessException e) {
        throw new Error("Method became inaccessible: " + event, e);
    } catch (InvocationTargetException e) {
        if (e.getCause() instanceof Error) {
            throw (Error) e.getCause();
        }
        throw e;
    }
}

3.3宵呛,SubscriberRegistry

  • SubscriberRegistry只有一個(gè)類就是SubscriberRegistry;
  • 該類可以注冊也可以取消注冊Subscriber夕凝;
  • 還需要注意的是宝穗,當(dāng)沒有@AllowConcurrentEvents注解時(shí),Subscriber使用的是SynchronizedSubscriber類型码秉,而有@AllowConcurrentEvents注解時(shí)逮矛,使用的是Subscriber類型;
    • 當(dāng)方法被調(diào)用時(shí)都是調(diào)用invokeSubscriberMethod方法转砖;
    • SynchronizedSubscriber類繼承了Subscriber類须鼎,并重寫了invokeSubscriberMethod方法鲸伴;
    • 不同的是SynchronizedSubscriber類型對方法使用了同步鎖,導(dǎo)致的結(jié)果就是晋控,沒有@AllowConcurrentEvents注解時(shí)invokeSubscriberMethod方法會在多個(gè)線程中同步執(zhí)行汞窗;
static final class SynchronizedSubscriber extends Subscriber {

    private SynchronizedSubscriber(EventBus bus, Object target, Method method) {
        super(bus, target, method);
    }

    @Override
    void invokeSubscriberMethod(Object event) throws InvocationTargetException {
        synchronized (this) {
            super.invokeSubscriberMethod(event);
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赡译,隨后出現(xiàn)的幾起案子仲吏,更是在濱河造成了極大的恐慌,老刑警劉巖蝌焚,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裹唆,死亡現(xiàn)場離奇詭異,居然都是意外死亡只洒,警方通過查閱死者的電腦和手機(jī)许帐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來红碑,“玉大人舞吭,你說我怎么就攤上這事∥錾海” “怎么了羡鸥?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長忠寻。 經(jīng)常有香客問我惧浴,道長,這世上最難降的妖魔是什么奕剃? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任衷旅,我火速辦了婚禮,結(jié)果婚禮上纵朋,老公的妹妹穿的比我還像新娘柿顶。我一直安慰自己,他們只是感情好操软,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布嘁锯。 她就那樣靜靜地躺著,像睡著了一般聂薪。 火紅的嫁衣襯著肌膚如雪家乘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天藏澳,我揣著相機(jī)與錄音仁锯,去河邊找鬼。 笑死翔悠,一個(gè)胖子當(dāng)著我的面吹牛业崖,可吹牛的內(nèi)容都是我干的野芒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼腻要,長吁一口氣:“原來是場噩夢啊……” “哼复罐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起雄家,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胀滚,沒想到半個(gè)月后趟济,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咽笼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年顷编,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剑刑。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡媳纬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出施掏,到底是詐尸還是另有隱情钮惠,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布七芭,位于F島的核電站素挽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狸驳。R本人自食惡果不足惜预明,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耙箍。 院中可真熱鬧撰糠,春花似錦、人聲如沸辩昆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卤材。三九已至遮斥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扇丛,已是汗流浹背术吗。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帆精,地道東北人较屿。 一個(gè)月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓隧魄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親隘蝎。 傳聞我的和親對象是個(gè)殘疾皇子购啄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355

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

  • 我從去年開始使用 RxJava ,到現(xiàn)在一年多了嘱么。今年加入了 Flipboard 后狮含,看到 Flipboard 的...
    Jason_andy閱讀 5,473評論 7 62
  • 轉(zhuǎn)一篇文章 原地址:http://gank.io/post/560e15be2dca930e00da1083 前言...
    jack_hong閱讀 915評論 0 2
  • 來自于:CSDNblog.csdn.net/caihongdao123/article/details/51897...
    于加澤閱讀 1,279評論 0 5
  • 從來不知道一米五幾的自己也會被說太要強(qiáng)。 而且是從你的嘴里曼振。 我應(yīng)該早點(diǎn)醒悟的几迄。 見面那天,我蹬著高跟鞋繞著會場跑...
    劉怪怪閱讀 320評論 0 2
  • 月底又趕上季度結(jié)束冰评,公司的事映胁,都催的緊。一天什么都不寫的話甲雅,感覺一天匆匆而過解孙,都沒有什么值得留戀的。 公司里的...
    右右多閱讀 150評論 2 2