EventExecutorGroup和ThreadPoolExecutor的比较

前言

这是我从另外一篇分析EventLoop的文章中摘出来的,感觉比较独立。

EventExecutorGroup和EventExecutor

简单来说,EventExecutorGroup就是一个线程池,EventExecutor是它里面执行任务的最小单元。
它的运行机制图如下:
在这里插入图片描述
这两个接口的默认实现上面提到过,上面的机制肯定来自于具体的实现类,可以对照这具体实现类进行看。
解释一下图里面的重点内容:

  1. EventExecutorGroup里面包含了n个EventExecutor,n需要在初始化的时候就指定。
  2. 在EventExecutorGroup提交的任务,它都会选择组内一个EventExecutor(顺序轮询选择)去执行,所以运行机制的重点是EventExecutor。
  3. EventExecutor内部会和一个线程进行关联,当提交第一个任务的时候,线程启动并且不停运行,除非外界执行关闭操作。
  4. EventExecutor内部有两个队列:taskQueue和scheduleTaskQueue,前者是一个阻塞队列,存储提交的普通task(用execute或者submit方法提交的);后者是一个优先级队列(线程不安全),存储的是调度任务(用schedule方法提交的),它会把最早应该执行的任务放在队首,保证peek出来的一定是队列优先级最高的。
  5. 提交普通task的时候,直接加进队列;提交定时任务的时候,当前线程是关联线程的话,加到调度队列中,否则提交一个普通task,task的内容就是:把调度任务加入调度队列中。
  6. 关联的线程一直在循环做一件事情:取任务,然后执行。
  7. 取任务的过程,图中应该比较清晰,它的具体代码实现是在类SingleThreadEventExecutor的takeTask()方法,可以结合在一起看。

关于组和成员的思考

EventExecutorGroup是个组,而EventExecutor是组里面的成员,那么它是如何来管理的呢?这也是我在学习过程中比较关心的点,也是我刚开始看类图的时候比较疑惑的点。

特点描述

组和成员之间的关系有如下特点:

  • 组拥有成员的一部分功能
    • 其中一些功能的具体实现需要依靠成员去完成,比如提交任务
    • 其中一些功能与成员表示的含义层次不同,比如关闭等方法。
  • 成员拥有组的所有功能
  • 成员比组多一些额外的功能

基于如上特点,才有了EventExecutorGroup与EventExecutor的继承关系。
当组的功能大于成员,并且放在成员上特别不合适的时候(比如类似于next和iterator这些的,成员方法实现这些方法的时候,直接返回的就是本身),继承关系是不是就应该反过来了,或许吧,还没有见过。

最大疑惑

刚开始看类图的时候,我最想不明白的地方在于它们的继承关系,我的直觉上认为正确的应该是反着的:EventExecutorGroup应该继承EventExecutor。所以才导致分析类图非常辛苦。

根源在于我平时经常使用的策略模式,策略模式它必须有一个上下文对象来维护对实际策略的一个引用。然后外界通过调用上下文的某个方法然后去真正执行实际的策略。
为了更方便直观的调用(暂且就这个理由吧),我通常喜欢上下文也实现一下策略接口,这样的话,隐隐约约就会出现一种感觉:上下文在管理着策略;当策略更具体一些(具体的应用场景)的时候,这种感觉就会明显。
这种感觉导致的是:管理者需要实现成员的接口。这也是我刚看这边类图的时候的最大的疑惑。
最后硬着头皮学习完以后,我觉得我理解了。上面有关策略模式的感觉是错误的,它并没有存在一种管理的含义,仅仅只是一种聚合而已,更像是一种代理。和这边组以及成员的关系不一样。

不过是不是也会有“当组的功能大于成员”的情况呢?继承关系是会反过来的吧。

eventLoop与EventLoopGroup的关系也是如此。

ThreadPoolExecutor的运行机制

当然我意识到EventExecutorGroup是一个线程池的时候,脑子里面立马就想起来了ThreadPoolExecutor,这不就是我们经常用的线程池吗?那他们有什么区别呢。我就顺便做了一下对比。
能用一张图说明白的事情,绝不大篇幅描述;先上一个图:
在这里插入图片描述
关于这个类的分析,包括运行机制和每一行的代码,博客还是比较多的,大家说的都挺对的。我不做具体分析了,简单强调几个我认为比较重要的点吧。

  • 线程分为核心和非核心,但是它并没有真正去赋予线程这样一个属性,而且当每一个线程执行任务结束以后通过当前运行的线程数与核心线程数的大小比较而得来的,运行的多了,那你这个线程就是非核心的,反之,你就是核心线程。
  • 针对不同线程类型,它们从队列里面取任务的策略也会有所不同,默认情况下,核心线程会使用take()方法无限阻塞从队列取任务,取不出来我就不行,一直等着。非核心线程会使用pool(long,TimeUnit)方法进行超时获取,超时时间是初始化时候配置的KeepAliveTime,如果超过这个点,还没有取到任务,说明任务有点少,那么当前线程就可以销毁了。这也就是最大存活时间了。
  • 但是如果将属性allowCoreThreadTimeOut置为true的话,核心线程的特殊待遇也就没有了,所有线程都一样了,都会超时获取,然后销毁。但一般不会这样配置的。

但是通过图解和解释,也不一定能够真正用懂线程池,看一下Java推荐的用法吧:
在这里插入图片描述
如果我们自己去配置参数,其实还是不太容易的,但是有一些属性,我们可以获取到,比如当
corePooSize=x,maxPoolSize=y,queue.size=z
那么你这个线程池正常情况下,会有x个线程去处理,也就是最多有x个任务正在执行,其它的都在等着。
任务比较多的时候,同时提交的任务超过了(x+z)个,那么同时可能会有x<n<=y个线程正在执行,n个任务被执行。但是这种配置仅支持(y+z)个任务在同一时刻执行。
说的也挺抽象的,但是可能会有一些用,比如配置tomcat或者quartz的参数的时候,如果自己要配置的话,根据自己情况调整吧。
当然支持(y+z)个任务执行并不是每s可以支撑这么多,它指的是同一时刻,那每秒有多少的话,就看任务的执行时间,比如每个任务只要10ms,那每秒就能处理100*(y+z)个任务。

EventExecutorGroup和ThreadPoolExecutor的比较

还是上图,相同点的话,并不是很严格,不用太纠结,哈哈。
在这里插入图片描述
都在图里面了,就不解释了。
既然有两个线程池了,那么应该用哪个呀?当然是ThreadPoolExecutor了,毫不犹豫。EventExecutorGroup毕竟是netty的实现,非netty的地方,应该用不到它,而且它在netty里面的使用场景也不多,确实有,但是那块还没有仔细看过。从功能上来说,还是jdk提供的功能多一些。

猜你喜欢

转载自blog.csdn.net/ywg_1994/article/details/103797761
今日推荐