Netty的深入浅出--38.继续底层分析NioEventLoopGroup(线程工厂的设计原理)
再来回顾一下EventLoopGroup和NIOEventLoopGroup:
EventLoopGroup继承于EventExecutorGroup
NioEventLoopGroup继承于MultithreadEventLoopGroup,而MultithreadEventLoopGroup又继承了MultithreadEventExecutorGroup
接上一章的分析,我们分析NioEventLoopGroup属性赋值最终是在MultiThreadEventExecutorGroup方法中进行的;
然后进入具体内容
查看newChild方法:
通过调用next()方法来创建EventExecutor类,这个类中的每个线程将会服务于MultithreadEventExecutorGroup类
它本身是返回的是NioEventLoop对象
我们现在来分析一下newChild这个方法,从它最开始的抽象类看起
它本身会接收一个Executor的参数。
它是根据通过方法传递过来的:
其实我们最开始分析的时候会发现EventExecutor是null,所以它是在下面被new出来的
不确定的话可以往回看一下:
我们分析一下ThreadperTaskExecutor,根据名字我们也可以看出来,是线程中每个任务的执行器。里面传入的参数newDefaultThreadFactory
这里我扯一下题外话,关于ThreadFatory它的工厂模式:
在传统方式创建线程使用runnable和thread来创建线程,将创建逻辑放入到run方法当中,然后通过调用thread的start方法才能开始执行程序。这个过程实际是把创建线程这件事情跟创建让线程去执行的逻辑的这两件事情给合在了一起,很显然我们提倡代码之间的解耦性,这种传统的方式是无法满足我们的需求的。
那么解决办法是:我们定义一个任务,这个任务是我们接下来指定要完成的业务逻辑,而对于线程的创建又是另外一个业务逻辑,这样的话,就可以实现线程的创建和需要完成的任务逻辑实现解耦。
如果不是很理解的话,我们继续往下分析newDefaultThreadFactory(),看它是如何实现工厂模式的:
它本身返回的是一个ThreadFactory
我们继续进入到DefaultThreadFactory类中构造器:
Thread.NORN_PRIORITY:线程优先级
查看Thread.NORN_PRIORITY源码,默认的线程优先级
我们接着上面那个this,然后跳转到:
显然之前那个false就是指不设定为守护线程(后台线程)
这里将poolType变量传入到toPoolName()方法中
进入到toPoolName()方法中:
进来后我们查看StringUtil.simpleClassName(poolType)
是string对象的通用工具类。
这里说一下题外话:包名后缀是internal官方一般不建议用户去使用。
我们继续关注我们的重点,simpleClassName()这个方法
简单描述:形成一个简化名字,类似于getSimpleName()方法,而且对于匿名类效果更好。作用就是返回包名。
我们进入到getSimpleName()方法:这个方法对于匿名类返回为空。而上面那个是对于匿名类效果更好
现在我们又回到toPoolName()的方法中
查看toLowerCase方法,这个不是很重要看一下就行了
如果poolName首字母是大写,第二个字母是小写,那就将poolName转换成小写:
又回到前面,总结来说就是将poolType这个clss对象转换成小写
继续往下看:
继续深入,前面两个我们已经讲过了,后面的threadGroup是从哪里来的呢:
往回找找到Thread.currentThread().getThreadGroup()方法
进入源码,返回的是当前线程所属的线程组,如果当前线程已经死亡掉了,那就返回null:
然后我们又回来:
如果getSecurityManager()不为null的话,就直接通过该方法获取当前线程组
接着我们又回到DefaultThreadFactory
poolId是一个原子操作
分析完之后,我们往回走:
我们分析一下ThreadFactory这个接口:
threadFactory可以根据需要创建新的线程,不必每次通过runnable创建线程。
最简单的实现方式:
现在我们继续往回走:newFeafaultThreadFactory()就是创建一个线程
现在我们看ThreadPerTaskExecutor
它本身实现了executor,构造方法的主要目的就是给它的成员变量threadFactory赋值
因为它实现了Executor接口,所以必须实现execute()方法,通过execute()方法来启动新线程(构造函数刚刚附上值的线程)。
这里使用到了两个设计模式:
命名设计模式:执行的命令交给了execute()方法中执行,而我们不必考虑它底层是怎么做的,我们只需要告诉它我们要完成的任务就行。
代理设计模式:本身应该由ThreadPerTaskExecutor来完成的事情,它将事情代理给了ThreadFactory来完成。
接下来我们看看executor
executor本身是一个对象,它的目的只是执行提交过来的runnable任务
实现了任务的提交和任务的运行的解耦
我们可以很正常的去创建一个线程,不用使用哪种明显的方式去创建,例如:new Thread(new (RunnaleTask())).start()。简单来说就是实现了很好的封装。
对于接口要求实现,在执行的时候并不严格要求是异步的,可以是同步。
简单来说:就是在执行提交线程里面执行了任务task。
这种方式比较极端,不怎么使用
在执行线程里面在创建一个线程。
组合的executor: