Netty源码分析 (一)----- NioEventLoopGroup
提到Netty首当其冲被提起的肯定是支持它承受高并发的线程模型,说到线程模型就不得不提到NioEventLoopGroup
这个线程池,接下来进入正题。
线程模型
首先来看一段Netty的使用示例
package com.wrh.server; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; public final class SimpleServer { public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new SimpleServerHandler())
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
}
}); ChannelFuture f = b.bind(8888).sync(); f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} private static class SimpleServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
} @Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelRegistered");
} @Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAdded");
}
}
}
下面将分析第一、二行代码,看下NioEventLoopGroup类的构造函数干了些什么。其余的部分将在其他博文中分析。
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
从代码中可以看到这里使用了两个线程池bossGroup
和workerGroup
,那么为什么需要定义两个线程池呢?这就要说到Netty的线程模型了。
Netty的线程模型被称为Reactor模型,具体如图所示,图上的mainReactor指的就是bossGroup
,这个线程池处理客户端的连接请求,并将accept的连接注册到subReactor的其中一个线程上;图上的subReactor当然指的就是workerGroup
,负责处理已建立的客户端通道上的数据读写;图上还有一块ThreadPool是具体的处理业务逻辑的线程池,一般情况下可以复用subReactor,比我的项目中就是这种用法,但官方建议处理一些较为耗时的业务时还是要使用单独的ThreadPool。
NioEventLoopGroup构造函数
NioEventLoopGroup的构造函数的代码如下
public NioEventLoopGroup() {
this(0);
} public NioEventLoopGroup(int nThreads) {
this(nThreads, null);
} public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
this(nThreads, threadFactory, SelectorProvider.provider());
} public NioEventLoopGroup(
int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) {
super(nThreads, threadFactory, selectorProvider);
}
NioEventLoopGroup类中的构造函数最终都是调用的父类MultithreadEventLoopGroup如下的构造函数:
protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
super(nThreads == 0? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
}
从上面的构造函数可以得到 如果使用EventLoopGroup workerGroup = new NioEventLoopGroup()
来创建对象,即不指定线程个数,则netty给我们使用默认的线程个数,如果指定则用我们指定的线程个数。
默认线程个数相关的代码如下:
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
而SystemPropertyUtil.getInt函数的功能为:得到系统属性中指定key(这里:key=”io.netty.eventLoopThreads”)所对应的value,如果获取不到获取失败则返回默认值,这里的默认值为:cpu的核数的2倍。
结论:如果没有设置程序启动参数(或者说没有指定key=”io.netty.eventLoopThreads”的属性值),那么默认情况下线程的个数为cpu的核数乘以2。
继续看,由于MultithreadEventLoopGroup的构造函数是调用的是其父类MultithreadEventExecutorGroup的构造函数,因此,看下此类的构造函数
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
} if (threadFactory == null) {
threadFactory = newDefaultThreadFactory();
} children = new SingleThreadEventExecutor[nThreads];
//根据线程个数是否为2的幂次方,采用不同策略初始化chooser
if (isPowerOfTwo(children.length)) {
chooser = new PowerOfTwoEventExecutorChooser();
} else {
chooser = new GenericEventExecutorChooser();
}
//产生nTreads个NioEventLoop对象保存在children数组中
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(threadFactory, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
//如果newChild方法执行失败,则对前面执行new成功的几个NioEventLoop进行shutdown处理
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
} for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
}
该构造函数干了如下三件事:
1、产生了一个线程工场:threadFactory = newDefaultThreadFactory();
MultithreadEventExecutorGroup.java
protected ThreadFactory newDefaultThreadFactory() {
return new DefaultThreadFactory(getClass());//getClass()为:NioEventLoopGroup.class
} DefaultThreadFactory.java
public DefaultThreadFactory(Class<?> poolType) {
this(poolType, false, Thread.NORM_PRIORITY);
}
2、根据线程个数是否为2的幂次方,采用不同策略初始化chooser
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
3、 产生nTreads个NioEventLoop对象保存在children数组中 ,线程都是通过调用newChild方法来产生的。
@Override
protected EventExecutor newChild(
ThreadFactory threadFactory, Object... args) throws Exception {
return new NioEventLoop(this, threadFactory, (SelectorProvider) args[0]);
}
这里传给NioEventLoop构造函数的参数为:NioEventLoopGroup、DefaultThreadFactory、SelectorProvider。
NioEventLoop构造函数分析
既然上面提到来new一个NioEventLoop对象,下面我们就看下这个类以及其父类。
NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider) {
super(parent, threadFactory, false);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
provider = selectorProvider;
selector = openSelector();
}
继续看父类 SingleThreadEventLoop的构造函数
protected SingleThreadEventLoop(EventLoopGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
super(parent, threadFactory, addTaskWakesUp);
}
又是直接调用来父类SingleThreadEventExecutor的构造函数,继续看
protected SingleThreadEventExecutor(
EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) { if (threadFactory == null) {
throw new NullPointerException("threadFactory");
} this.parent = parent;
this.addTaskWakesUp = addTaskWakesUp;//false
thread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
boolean success = false;
updateLastExecutionTime();
try {
//调用NioEventLoop类的run方法
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
for (;;) {
int oldState = STATE_UPDATER.get(SingleThreadEventExecutor.this);
if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
break;
}
}
// Check if confirmShutdown() was called at the end of the loop.
if (success && gracefulShutdownStartTime == 0) {
logger.error(
"Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must be called " +
"before run() implementation terminates.");
} try {
// Run all remaining tasks and shutdown hooks.
for (;;) {
if (confirmShutdown()) {
break;
}
}
} finally {
try {
cleanup();
} finally {
STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
threadLock.release();
if (!taskQueue.isEmpty()) {
logger.warn(
"An event executor terminated with " +
"non-empty task queue (" + taskQueue.size() + ')');
} terminationFuture.setSuccess(null);
}
}
}
}
}); taskQueue = newTaskQueue();
}
protected Queue<Runnable> newTaskQueue() {
return new LinkedBlockingQueue<Runnable>();
}
主要干如下两件事:
1、利用ThreadFactory创建来一个Thread,传入了一个Runnable对象,该Runnable重写的run代码比较长,不过重点仅仅是调用NioEventLoop类的run方法。
2、使用LinkedBlockingQueue类初始化taskQueue 。
其中,newThread方法的代码如下:
DefaultThreadFactory.java
@Override
public Thread newThread(Runnable r) {
Thread t = newThread(new DefaultRunnableDecorator(r), prefix + nextId.incrementAndGet()); try {
//判断是否是守护线程,并进行设置
if (t.isDaemon()) {
if (!daemon) {
t.setDaemon(false);
}
} else {
if (daemon) {
t.setDaemon(true);
}
}
//设置其优先级
if (t.getPriority() != priority) {
t.setPriority(priority);
}
} catch (Exception ignored) {
// Doesn't matter even if failed to set.
}
return t;
} protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(r, name);
}
FastThreadLocalThread.java
public FastThreadLocalThread(Runnable target, String name) {
super(target, name);// FastThreadLocalThread extends Thread
}
到这里,可以看到底层还是借助于类似于Thread thread = new Thread(r)这种方式来创建线程。
关于NioEventLoop对象可以得到的点有,初始化了如下4个属性。
1、NioEventLoopGroup (在父类SingleThreadEventExecutor中)
2、selector
3、provider
4、thread (在父类SingleThreadEventExecutor中)
总结
关于NioEventLoopGroup,总结如下
1、 如果不指定线程数,则线程数为:CPU的核数*2
2、根据线程个数是否为2的幂次方,采用不同策略初始化chooser
3、产生nThreads个NioEventLoop对象保存在children数组中。
可以理解NioEventLoop就是一个线程,线程NioEventLoop中里面有如下几个属性:
1、NioEventLoopGroup (在父类SingleThreadEventExecutor中)
2、selector
3、provider
4、thread (在父类SingleThreadEventExecutor中)
更通俗点就是:NioEventLoopGroup就是一个线程池,NioEventLoop就是一个线程。NioEventLoopGroup线程池中有N个NioEventLoop线程。
Netty源码分析 (一)----- NioEventLoopGroup的更多相关文章
- Netty源码分析第2章(NioEventLoop)---->第1节: NioEventLoopGroup之创建线程执行器
Netty源码分析第二章: NioEventLoop 概述: 通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各个 ...
- Netty源码分析第2章(NioEventLoop)---->第2节: NioEventLoopGroup之NioEventLoop的创建
Netty源码分析第二章: NioEventLoop 第二节: NioEventLoopGroup之NioEventLoop的创建 回到上一小节的MultithreadEventExecutorG ...
- Netty源码分析(前言, 概述及目录)
Netty源码分析(完整版) 前言 前段时间公司准备改造redis的客户端, 原生的客户端是阻塞式链接, 并且链接池初始化的链接数并不高, 高并发场景会有获取不到连接的尴尬, 所以考虑了用netty长 ...
- Netty源码分析第1章(Netty启动流程)---->第1节: 服务端初始化
Netty源码分析第一章: Server启动流程 概述: 本章主要讲解server启动的关键步骤, 读者只需要了解server启动的大概逻辑, 知道关键的步骤在哪个类执行即可, 并不需要了解每一步的 ...
- Netty源码分析第1章(Netty启动流程)---->第2节: NioServerSocketChannel的创建
Netty源码分析第一章: Server启动流程 第二节:NioServerSocketChannel的创建 我们如果熟悉Nio, 则对channel的概念则不会陌生, channel在相当于一个通 ...
- Netty源码分析第1章(Netty启动流程)---->第3节: 服务端channel初始化
Netty源码分析第一章:Netty启动流程 第三节:服务端channel初始化 回顾上一小节的initAndRegister()方法: final ChannelFuture initAndRe ...
- Netty源码分析第1章(Netty启动流程)---->第4节: 注册多路复用
Netty源码分析第一章:Netty启动流程 第四节:注册多路复用 回顾下以上的小节, 我们知道了channel的的创建和初始化过程, 那么channel是如何注册到selector中的呢?我们继 ...
- Netty源码分析第1章(Netty启动流程)---->第5节: 绑定端口
Netty源码分析第一章:Netty启动步骤 第五节:绑定端口 上一小节我们学习了channel注册在selector的步骤, 仅仅做了注册但并没有监听事件, 事件是如何监听的呢? 我们继续跟第一小节 ...
- Netty源码分析第2章(NioEventLoop)---->第3节: 初始化线程选择器
Netty源码分析第二章:NioEventLoop 第三节:初始化线程选择器 回到上一小节的MultithreadEventExecutorGroup类的构造方法: protected Multi ...
- Netty源码分析第2章(NioEventLoop)---->第4节: NioEventLoop线程的启动
Netty源码分析第二章: NioEventLoop 第四节: NioEventLoop线程的启动 之前的小节我们学习了NioEventLoop的创建以及线程分配器的初始化, 那么NioEvent ...
随机推荐
- HTML5-新增语义化结构标签
总结目录结构: 1.简洁的DOCTYPE声明 2.新的布局结构标签 header,article,section,aside,footer 3.新的其它常用标签: nav,hgroup,figure, ...
- 那些年,想和你一起认识的SpringCloud Eureka
前几天鲁班LB跟我说:你玩把游戏都要半个钟啦,为何不用这时间来看看书,如果涨工资还可以帮我买个皮肤. 面对如此合理的这需求,但我不以为然,事实上并不是我不想学习,而是 ↓ 实力不允许呀~ 直到有一天, ...
- LVS + Keepalived + Nginx基于DR模式构建高可用方案
在大型网站中一般服务端会做集群,同时利用负载均衡器做负载均衡.这样有利于将大量的请求分散到各个服务器上,提升网站的响应速度.当然为了解决单点故障的问题,还会做热备份方案.这里演示利用LVS做负载均衡器 ...
- poj 1068 模拟
题目链接 大概题意就是告诉你有个n个小括号,每一个")"左边有多少个"("都告诉你了,然后让你求出每一对括号之间有多少对括号(包含自己本身). 思路: 我先计算 ...
- input属性设置type="number"之后, 仍可输入e, E, -, + 的解决办法
<el-input v-model="scope.row.variables.leaderbuweiscores.score" @keyup.native="cha ...
- python 处理json数据
python 处理 json数据 以下是登录账号后获取的json数据,headers中注意加入cookie值 需要处理的数据如下: 全部代码如下 #!/usr/bin/env python # -*- ...
- Apache Flink 1.9 重大特性提前解读
今天在 Apache Flink meetup ·北京站进行 Flink 1.9 重大新特性进行了讲解,两位讲师分别是 戴资力/杨克特,zhisheng 我也从看完了整个 1.9 特性解读的直播,预计 ...
- 初识代理——Proxy
无处不在的模式——Proxy 最近在看<设计模式之禅>,看到代理模式这一章的时候,发现自己在写spring项目的时候其实很多时候都用到了代理,无论是依赖注入.AOP还是其他,可以说是无处不 ...
- 第十章 Centos7-系统进程管理 随堂笔记
第十章 Centos7-系统进程管理 本节所讲内容: 10.1 进程概述和ps查看进程工具 10.2 uptime查看系统负载-top动态管理进程 10.3 前后台进程切换- nice进程优先级-实战 ...
- 在一个含有1-n的序列中,每次找到第Ki小的数,并把它删除(线段树)
提交链接 Data structure is one of the basic skills for Computer Science students, which is a particular ...