Guava事件总线EventBus阻塞行为解读

google guava库的事件总线组件EventBus,是观察者模式(进程内事件发布/订阅)的一个比较优雅的实现。之前项目一直没什么机会使用(之前事件解耦多用消息中间件),最近自个儿摸索DDD,在进程内领域事件的实现时,就采用了EventBus。总体使用比较顺利,其提供的api也比较简单,在这里就不详诉使用方式了。今天就如标题所说,谈谈EventBus post事件时的阻塞行为。

具体遇到的令我困惑的行为是,在不同线程里post事件,貌似也会相互阻塞。以下是实验代码:

 public static void main(String[] args) throws InterruptedException {
        EventBus eventBus = new EventBus();
        eventBus.register(new Object(){
            @Subscribe
            public void onPublish(Object event) throws InterruptedException {
                TimeUnit.SECONDS.sleep(5);
                System.out.println("事件处理了,thread id is " + Thread.currentThread().getId());
            }
        });
		// 新开一个线程post事件
        new Thread(() -> {
            eventBus.post(new Object());
            System.out.println("事件发布了---1111");
        }).start();

        eventBus.post(new Object());
        System.out.println("事件发布了---2222");

        TimeUnit.HOURS.sleep(1);
    }

以上代码中,我新开了一个线程去post事件,按照我原先的设想,事件订阅代码应该是差不多同时执行,但是事实是在一个事件post完5秒后另一个post方法才返回成功。难道是post(Object event)这个方法是相互阻塞的?同一时间只能post一个event,不能并发post?
带着问题翻源码,发现post的调度并没有阻塞的地方,以下是post源码:

public void post(Object event) {
    Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
    if (eventSubscribers.hasNext()) {
      dispatcher.dispatch(event, eventSubscribers);
    } else if (!(event instanceof DeadEvent)) {
      // the event had no subscribers and was not itself a DeadEvent
      post(new DeadEvent(this, event));
    }
  }

主要委托给dispatcher的dispatch调用,EventBus使用的dispatcher是PerThreadQueuedDispatcher,它的特点是内部的事件队列使用了ThreadLocal,线程之间的事件是相互隔离的,所以不存在阻塞

/** Per-thread queue of events to dispatch. */
  private final ThreadLocal<Queue<Event>> queue =
      new ThreadLocal<Queue<Event>>() {
        @Override
        protected Queue<Event> initialValue() {
          return Queues.newArrayDeque();
        }
      };

继续往下看PerThreadQueuedDispatcher.dispatch

 @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();
      }
    }
  }

总体逻辑就是event入队(queue是ThradLocal变量,线程之间隔离,不会阻塞),然后从队列取任务分发执行,dispatching也是一个ThradLocal变量,应该是为了防止listener的逻辑中重复发布相同事件导致死循环。看到这里还是看不出阻塞在哪里,继续往下看

	/** Dispatches {@code event} to this subscriber using the proper executor. */
  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));
            }
          }
        });
  }

这里的exector就是DirectExecutor,在当前线程下执行:

@GwtCompatible
enum DirectExecutor implements Executor {
INSTANCE;

@Override
public void execute(Runnable command) {
  command.run();
}

@Override
public String toString() {
  return "MoreExecutors.directExecutor()";
}
}

看来真正阻塞的地方是在invokeSubscriberMethod(event);这个方法里了,
继续往下,发现SubScriber有两个实现,SubscriberSynchronizedSubscriber,他们都是在SubScriber的静态方法create()中使用的

 /** Creates a {@code Subscriber} for {@code method} on {@code listener}. */
  static Subscriber create(EventBus bus, Object listener, Method method) {
    return isDeclaredThreadSafe(method)
        ? new Subscriber(bus, listener, method)
        : new SynchronizedSubscriber(bus, listener, method);
  }

isDeclaredThreadSafe方法是判断注册的监听方法有没有AllowConcurrentEvents这个注解

 private static boolean isDeclaredThreadSafe(Method method) {
  return method.getAnnotation(AllowConcurrentEvents.class) != null;
}

如果有就创建Subscriber 否则 创建SynchronizedSubscriber,后者相比前者,区别就是调用目标方法是使用了synchronize关键字,从而达到了阻塞的效果

@Override
  void invokeSubscriberMethod(Object event) throws InvocationTargetException {
    synchronized (this) {
      super.invokeSubscriberMethod(event);
    }
  }

至此,问题答案终于知晓,post方法之所以被阻塞,不是生产者发布事件时阻塞而是消费者消费事件时使用了synchronize关键字实现了同步消费的效果,回到文章开始的实验,只要消费方法添加AllowConcurrentEvents注解,就不会出现阻塞的效果了,实现了并发消费。

			@Subscribe
			@AllowConcurrentEvents
            public void onPublish(Object event) throws InterruptedException {
                TimeUnit.SECONDS.sleep(5);
                System.out.println("事件处理了,thread id is " + Thread.currentThread().getId());
            }
            
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Spring Boot中集成EventBus可以通过以下步骤进行: 1. 添加EventBus依赖:在`pom.xml`文件中添加EventBus的依赖,例如: ```xml <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1-jre</version> </dependency> ``` 2. 创建事件类:创建表示事件的类,例如: ```java public class MyEvent { private String message; public MyEvent(String message) { this.message = message; } public String getMessage() { return message; } } ``` 3. 创建事件监听器:创建事件监听器类,用于处理事件,例如: ```java @Component public class MyEventListener { @Subscribe public void handleEvent(MyEvent event) { // 处理事件逻辑 System.out.println("Received event: " + event.getMessage()); } } ``` 4. 配置EventBus:在Spring Boot的配置类中配置EventBus,例如: ```java @Configuration public class EventBusConfig { @Bean public EventBus eventBus() { return new EventBus(); } @Autowired public void setListeners(EventBus eventBus, MyEventListener listener) { eventBus.register(listener); } } ``` 5. 发布事件:在需要发布事件的地方,注入EventBus并发布事件,例如: ```java @Service public class MyService { @Autowired private EventBus eventBus; public void doSomething() { // 执行业务逻辑 // ... // 发布事件 eventBus.post(new MyEvent("Hello EventBus!")); } } ``` 这样,当`MyService`中的`doSomething`方法被调用时,触发`MyEvent`事件,并由`MyEventListener`监听器处理该事件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值