相关文章链接
processSelectedKeys() vs runAllTasks()
NioServerSocketChannel-Unsafe初始化详解
ThreadPerTaskExecutor 与线程创建详解
一、从 NioEventLoopGroup 初始化说起
我们在使用 Netty 的时候,通常会这样创建 NioEventLoopGroup:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
在 NioEventLoopGroup 的初始化过程中,会经过一系列的构造方法调用,最终到达 MultithreadEventExecutorGroup 的构造方法。在这个构造方法中,有一段非常关键的代码:
// MultithreadEventExecutorGroup.java
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory,
Object... args) {
// 参数校验
if (nThreads <= 0) {
throw new IllegalArgumentException(
String.format("nThreads: %d (expected: > 0)", nThreads));
}
// 重点:如果 executor 为 null,创建默认的 ThreadPerTaskExecutor
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
// ... 后续代码
}
这里的 if (executor == null) 判断非常重要,因为我们在创建 NioEventLoopGroup 的时候没有传入 Executor,所以这里会创建一个默认的 ThreadPerTaskExecutor。这个 ThreadPerTaskExecutor 就是负责为每个 NioEventLoop 创建线程的关键组件。
二、ThreadPerTaskExecutor 是什么
我们先来看 ThreadPerTaskExecutor 的源码:
// ThreadPerTaskExecutor.java
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
this.threadFactory = threadFactory;
}
@Override
public void execute(Runnable command) {
// 每次调用 execute,都会创建一个新线程并启动
threadFactory.newThread(command).start();
}
}
ThreadPerTaskExecutor 实现了 JDK 的 Executor 接口,但它和我们常用的 ThreadPoolExecutor 完全不同。ThreadPoolExecutor 是线程池,会维护一个线程池,任务提交后会被分配到池中的线程执行。而 ThreadPerTaskExecutor 每次调用 execute() 方法都会创建一个新线程。
这里有个关键点:ThreadPerTaskExecutor 不是用来执行普通任务的,它只是用来为每个 NioEventLoop 创建工作线程。每个 NioEventLoop 只会在启动的时候调用一次 executor.execute(),创建一个线程后就一直复用这个线程,不会再创建新线程了。所以不回造成资源的浪废
三、ThreadFactory 的创建过程
现在我们来看 ThreadPerTaskExecutor 的构造方法参数 threadFactory 是怎么来的。回到上面的代码:
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
这里调用了 newDefaultThreadFactory() 方法,我们来看这个方法的实现:
// MultithreadEventExecutorGroup.java
protected ThreadFactory newDefaultThreadFactory() {
return new DefaultThreadFactory(getClass());
}
这个方法很简单,就是创建了一个 DefaultThreadFactory 实例,传入的参数是当前类的 Class 对象。因为我们创建的是 NioEventLoopGroup,所以这里的 getClass() 返回的是 NioEventLoopGroup.class。
四、DefaultThreadFactory 的实现
现在我们来详细看看 DefaultThreadFactory 是如何实现的:
// DefaultThreadFactory.java
public class DefaultThreadFactory implements ThreadFactory {
// 静态的 poolId 计数器,所有 DefaultThreadFactory 实例共享
// 每创建一个 DefaultThreadFactory,poolId 就会递增
private static final AtomicInteger poolId = new AtomicInteger();
// 线程编号计数器,每个 DefaultThreadFactory 实例独有
// 每创建一个线程,nextId 就会递增
private final AtomicInteger nextId = new AtomicInteger();
// 线程名前缀,例如:nioEventLoopGroup-2-
private final String prefix;
// 是否是守护线程
private final boolean daemon;
// 线程优先级
private final int priority;
// 线程组
protected final ThreadGroup threadGroup;
// 构造方法1:传入 Class 对象
public DefaultThreadFactory(Class<?> poolType) {
this(poolType, false, Thread.NORM_PRIORITY);
}
// 构造方法2:传入 Class 对象、是否守护线程、优先级
public DefaultThreadFactory(Class<?> poolType, boolean daemon, int priority) {
this(toPoolName(poolType), daemon, priority);
}
// 构造方法3:真正做事的构造方法
public DefaultThreadFactory(String poolName, boolean daemon, int priority) {
// 生成线程名前缀
// poolId.incrementAndGet() 会让 poolId 递增并返回新值
// 例如:nioEventLoopGroup-2-
prefix = poolName + '-' + poolId.incrementAndGet() + '-';
// 保存是否守护线程的标志,默认是 false(非守护线程)
this.daemon = daemon;
// 保存线程优先级,默认是 Thread.NORM_PRIORITY(5)
this.priority = priority;
// 获取线程组
// 如果没有安全管理器,使用当前线程的线程组
// 如果有安全管理器,使用安全管理器的线程组
threadGroup = System.getSecurityManager() == null ?
Thread.currentThread().getThreadGroup() :
System.getSecurityManager().getThreadGroup();
}
// 将 Class 转换为线程池名称
private static String toPoolName(Class<?> poolType) {
// 获取类的简单名称,例如 NioEventLoopGroup
String poolName = poolType.getSimpleName();
// 将首字母转换为小写
// 例如:NioEventLoopGroup -> nioEventLoopGroup
return poolName.isEmpty() ? "unknown" :
Character.toLowerCase(poolName.charAt(0)) + poolName.substring(1);
}
@Override
public Thread newThread(Runnable r) {
// 创建新线程
// threadGroup: 线程组
// r: 要执行的任务(Runnable)
// prefix + nextId.incrementAndGet(): 线程名,例如 nioEventLoopGroup-2-1
// 0: 栈大小,0 表示使用默认值
Thread t = new Thread(threadGroup, r,
prefix + nextId.incrementAndGet(), 0);
// 设置是否为守护线程
// 如果当前线程的守护状态和期望的不一致,就设置一下
if (t.isDaemon() != daemon) {
t.setDaemon(daemon);
}
// 设置线程优先级
// 如果当前线程的优先级和期望的不一致,就设置一下
if (t.getPriority() != priority) {
t.setPriority(priority);
}
return t;
}
}
五、线程名的生成规则
我们来详细说说线程名是怎么生成的。假设我们创建了两个 NioEventLoopGroup:
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 1 个线程
EventLoopGroup workerGroup = new NioEventLoopGroup(4); // 4 个线程
第一步:生成 poolName
在 DefaultThreadFactory 的构造方法中,会调用 toPoolName(poolType) 方法:
private static String toPoolName(Class<?> poolType) {
String poolName = poolType.getSimpleName(); // "NioEventLoopGroup"
return Character.toLowerCase(poolName.charAt(0)) + poolName.substring(1); // "nioEventLoopGroup"
}
所以 poolName 就是 "nioEventLoopGroup"。
第二步:生成 prefix
在 DefaultThreadFactory 的构造方法中,会生成 prefix:
prefix = poolName + '-' + poolId.incrementAndGet() + '-';
因为 poolId 是静态的,所有 DefaultThreadFactory 实例共享,所以:
- 第一个 NioEventLoopGroup(bossGroup)创建时,poolId 从 0 递增到 1,prefix =
"nioEventLoopGroup-1-" - 第二个 NioEventLoopGroup(workerGroup)创建时,poolId 从 1 递增到 2,prefix =
"nioEventLoopGroup-2-"
第三步:生成线程名
当调用 newThread() 方法创建线程时,会生成线程名:
Thread t = new Thread(threadGroup, r, prefix + nextId.incrementAndGet(), 0);
因为 nextId 是实例变量,每个 DefaultThreadFactory 实例独有,所以:
- bossGroup 的第 1 个线程:
"nioEventLoopGroup-1-1" - workerGroup 的第 1 个线程:
"nioEventLoopGroup-2-1" - workerGroup 的第 2 个线程:
"nioEventLoopGroup-2-2" - workerGroup 的第 3 个线程:
"nioEventLoopGroup-2-3" - workerGroup 的第 4 个线程:
"nioEventLoopGroup-2-4"
六、线程创建的完整流程
现在我们把整个流程串起来,看看一个 NioEventLoop 的线程是如何创建的:
1. NioEventLoopGroup 初始化
EventLoopGroup workerGroup = new NioEventLoopGroup();
2. 创建 ThreadPerTaskExecutor
在 MultithreadEventExecutorGroup 的构造方法中:
if (executor == null) {
// 调用 newDefaultThreadFactory() 创建 DefaultThreadFactory
// 然后用 DefaultThreadFactory 创建 ThreadPerTaskExecutor
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
3. 创建 NioEventLoop
在 MultithreadEventExecutorGroup 的构造方法中,会循环创建 NioEventLoop:
for (int i = 0; i < nThreads; i ++) {
// 调用 newChild() 方法创建 NioEventLoop
// 把 executor 传进去
children[i] = newChild(executor, args);
}
4. NioEventLoop 保存 executor
在 NioEventLoop 的构造方法中(实际上是在父类 SingleThreadEventExecutor 中):
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor, ...) {
this.parent = parent;
// 把 executor 保存起来,后面会用到
this.executor = executor;
}
注意:这里只是保存了 executor,还没有创建线程。
5. 触发线程创建
当 Channel 注册到 NioEventLoop 时,会调用 eventLoop.execute(task):
// SingleThreadEventExecutor.java
public void execute(Runnable task) {
boolean inEventLoop = inEventLoop();
addTask(task);
if (!inEventLoop) {
// 如果当前线程不是 EventLoop 线程,就启动 EventLoop 线程
startThread();
}
}
6. 启动线程
// SingleThreadEventExecutor.java
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
try {
doStartThread();
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_NOT_STARTED);
}
}
}
}
private void doStartThread() {
// 调用 ThreadPerTaskExecutor 的 execute 方法
executor.execute(new Runnable() {
@Override
public void run() {
// 保存线程引用
thread = Thread.currentThread();
// 执行 NioEventLoop 的 run() 方法
SingleThreadEventExecutor.this.run();
}
});
}
7. ThreadPerTaskExecutor 创建线程
// ThreadPerTaskExecutor.java
@Override
public void execute(Runnable command) {
// 调用 DefaultThreadFactory 的 newThread() 方法创建线程
// 然后调用 start() 启动线程
threadFactory.newThread(command).start();
}
8. DefaultThreadFactory 创建线程
// DefaultThreadFactory.java
@Override
public Thread newThread(Runnable r) {
// 创建新线程,线程名:nioEventLoopGroup-2-1
Thread t = new Thread(threadGroup, r,
prefix + nextId.incrementAndGet(), 0);
// 设置为非守护线程
if (t.isDaemon() != daemon) {
t.setDaemon(daemon);
}
// 设置线程优先级
if (t.getPriority() != priority) {
t.setPriority(priority);
}
return t;
}
9. 线程启动
调用 t.start() 后,线程启动,开始执行 Runnable 的 run() 方法,也就是 NioEventLoop 的 run() 方法。这个 run() 方法会一直循环,处理 I/O 事件和任务队列中的任务。
八、总结
整个线程创建的流程可以总结为:
- NioEventLoopGroup 初始化时,创建 ThreadPerTaskExecutor 和 DefaultThreadFactory
- 创建多个 NioEventLoop,每个 NioEventLoop 都持有 ThreadPerTaskExecutor
- 当 Channel 注册到 NioEventLoop 时,触发线程创建
- NioEventLoop 调用
executor.execute() - ThreadPerTaskExecutor 调用
threadFactory.newThread()创建线程 - DefaultThreadFactory 创建 Thread 对象,设置线程名、优先级等属性
- 调用
thread.start()启动线程 - 线程执行 NioEventLoop 的 run() 方法,开始循环处理事件
6887



被折叠的 条评论
为什么被折叠?



