Introduction
As an asynchronous NIO framework, multithreading is definitely its foundation, but for the actual users of netty, it is generally not necessary to touch multithreading. We only need to follow the process specified by the netty framework and customize the handler. to process the corresponding message.
Then some friends will ask, as a NIO framework, where is the multi-threading of netty reflected? What is the underlying principle of it?
Today, let's take a look at the task executors EventExecutor and EventExecutorGroup in netty.
EventExecutorGroup
Because EventExecutor inherits from EventExecutorGroup, let's first explain EventExecutorGroup in detail.
First look at the definition of EventExecutorGroup:
public interface EventExecutorGroup extends ScheduledExecutorService, Iterable<EventExecutor>
EventExecutorGroup inherits from JDK's ScheduledExecutorService, which can execute scheduled tasks or submit tasks for execution like ordinary task executors.
At the same time, EventExecutorGroup also inherits the Iterable interface, indicating that EventExecutorGroup is traversable, and its traversal object is EventExecutor.
EventExecutorGroup has two methods related to Iterable, next and iterator:
EventExecutor next();
@Override
Iterator<EventExecutor> iterator();
Calling the next method in EventExecutorGroup will return an EventExecutor object, so what is the relationship between EventExecutorGroup and EventExecutor?
Let's take a look at the definition of EventExecutor:
public interface EventExecutor extends EventExecutorGroup
You can see that EventExecutor is actually a subclass of EventExecutorGroup. But there is actually a reference to the subclass EventExecutor in the parent class EventExecutorGroup.
This design pattern of referencing and returning subclasses in the parent class Group is very common in netty. You can experience for yourself whether this design is good or bad.
EventExecutorGroup, as an EventExecutor Group object, is used to manage EventExecutors in the group. Therefore, some unified management interfaces for EventExecutor are designed in EventExecutorGroup.
For example, the boolean isShuttingDown()
method is used to determine that all EventExecutors in this group are shutting down or have been shut down.
In addition, EventExecutorGroupt provides all EventExector methods in the shutdown group: Future<?> shutdownGracefully()
and terminate methods: Future<?> terminationFuture()
.
Both of these methods return a Future, so we can consider these two methods to be asynchronous.
Other methods in EventExecutorGroup are some rewrites of ScheduledExecutorService methods in JDK, such as submit, schedule, scheduleAtFixedRate, scheduleWithFixedDelay, etc.
EventExecutor
Next, let's study EventExecutor. In the previous section, we briefly mentioned that EventExecutor inherits from EventExecutorGroup. Compared with EventExecutorGroup, what new methods does EventExecutor have?
We know that EventExecutorGroup inherits Iterable and defines a next method to return an EventExecutor object in the Group.
Because there are many EventExecutors in the Group, as for which EventExecutor to return, it is still implemented by the specific implementation class.
In EventExecutor, it overrides this method:
@Override
EventExecutor next();
The next method here returns the EventExecutor itself.
In addition, because EventExecutor is managed by EventExecutorGroup, there is also a parent method in EventExecutor that returns the EventExecutorGroup that manages EventExecutor:
EventExecutorGroup parent();
Two new inEventLoop methods have been added to EventExecutor to determine whether a given thread is executing in the event loop.
boolean inEventLoop();
boolean inEventLoop(Thread thread);
EventExecutor also provides two methods that can return Promise and ProgressivePromise.
<V> Promise<V> newPromise();
<V> ProgressivePromise<V> newProgressivePromise();
Friends familiar with ECMAScript may know that Promise is a new syntax feature introduced by ES6 to solve the problem of callback hell. The Promise introduced by netty here inherits from Future and adds two states of success and failure.
ProgressivePromise goes one step further and provides a progress based on Promise to represent progress.
In addition, EventExecutor also provides methods for encapsulating Succeeded results and Failed exceptions into Futures.
<V> Future<V> newSucceededFuture(V result);
<V> Future<V> newFailedFuture(Throwable cause);
Basic implementation of EventExecutorGroup in netty
EventExecutorGroup and EventExecutor have many very important implementations in netty, the most common of which are EventLoop and EventLoopGroup. Given the importance of EventLoop and EventLoopGroup, we will focus on them in later chapters. Here's a look at other implementations in netty.
The default implementation of EventExecutorGroup in netty is called DefaultEventExecutorGroup, and its inheritance relationship is as follows:
<img src="https://img-blog.csdnimg.cn/ae242899a8234b668045716daa2eec1a.png" style="zoom:67%;" />
You can see that DefaultEventExecutorGroup inherits from MultithreadEventExecutorGroup, which in turn inherits from AbstractEventExecutorGroup.
Let's first look at the logic of AbstractEventExecutorGroup. AbstractEventExecutorGroup is basically some implementation of the interface in EventExecutorGroup.
We know that a next() method is defined in EventExecutorGroup, which can return an EventExecutor in the Group.
In AbstractEventExecutorGroup, almost all methods in EventExecutorGroup are implemented by calling the next() method. Take the submit method as an example:
public Future<?> submit(Runnable task) {
return next().submit(task);
}
It can be seen that the submit method first calls the EventExecutor obtained by next, and then calls the submit method in the EventExecutor.
All other methods in AbstractEventExecutorGroup are implemented like this. However, the next() method is not implemented in the AbstractEventExecutorGroup. How to get the EventExecutor from the Group depends on the specific implementation of the underlying layer.
MultithreadEventExecutorGroup inherits from AbstractEventExecutorGroup and provides support for multithreaded tasks.
MultithreadEventExecutorGroup has two types of constructors. The number of multithreads can be specified in the constructor, as well as the task executor Executor. If no Executor is provided, a ThreadFactory can be provided. MultithreadEventExecutorGroup will call new ThreadPerTaskExecutor(threadFactory)
to construct an Executor for each thread:
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
this(nThreads, threadFactory == null ? null : new ThreadPerTaskExecutor(threadFactory), args);
}
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
How does MultithreadEventExecutorGroup support multithreading?
First, MultithreadEventExecutorGroup provides two children, children and readonlyChildren:
private final EventExecutor[] children;
private final Set<EventExecutor> readonlyChildren;
There is a one-to-one correspondence between children and the number of threads in the MultithreadEventExecutorGroup. As many threads as there are, the children are as big as they are.
children = new EventExecutor[nThreads];
Then by calling the newChild method, the incoming executor is constructed as an EventExecutor and returned:
children[i] = newChild(executor, args);
Take a look at the definition of the newChild method:
protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;
This method is not implemented in MultithreadEventExecutorGroup, it needs to be implemented in a more specific class.
readonlyChildren is a read-only version of child that is returned in the traversal method:
readonlyChildren = Collections.unmodifiableSet(childrenSet);
public Iterator<EventExecutor> iterator() {
return readonlyChildren.iterator();
}
We now have all the EventExecutors in the Group, so in the MultithreadEventExecutorGroup, how does the next method choose which EventExecutor to return?
First look at the definition of the next method:
private final EventExecutorChooserFactory.EventExecutorChooser chooser;
chooser = chooserFactory.newChooser(children);
public EventExecutor next() {
return chooser.next();
}
The next method calls the chooser's next method. Let's take a look at the specific implementation of the chooser's next method:
public EventExecutor next() {
return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
}
As you can see, it is actually a very simple operation to obtain objects based on index.
Finally, take a look at the implementation of the newChild method in DefaultEventExecutorGroup:
protected EventExecutor newChild(Executor executor, Object... args) throws Exception {
return new DefaultEventExecutor(this, executor, (Integer) args[0], (RejectedExecutionHandler) args[1]);
}
The EventExecutor returned by newChild uses DefaultEventExecutor. This class is the default implementation of EventExecutor in netty, which we explain in detail in the next summary.
Basic implementation of EventExecutor in netty
The default implementation of EventExecutor in netty is DefaultEventExecutor, first look at its inheritance structure:
<img src="https://img-blog.csdnimg.cn/67a3434b3dc14e1d98baf8cb5dbf07cf.png" style="zoom:67%;" />
DefaultEventExecutor inherits from SingleThreadEventExecutor, and SingleThreadEventExecutor inherits from AbstractScheduledEventExecutor, and AbstractScheduledEventExecutor inherits from AbstractEventExecutor.
Let's take a look at the definition of AbstractEventExecutor:
public abstract class AbstractEventExecutor extends AbstractExecutorService implements EventExecutor
AbstractEventExecutor inherits AbstractExecutorService and implements the EventExecutor interface.
AbstractExecutorService is a class in JDK that provides some implementations of ExecutorService, such as submit, invokeAny and invokeAll methods.
As a member of ExecutorGroup, AbstractEventExecutor provides a parent property of type EventExecutorGroup:
private final EventExecutorGroup parent;
public EventExecutorGroup parent() {
return parent;
}
For the next method, AbstractEventExecutor returns itself:
public EventExecutor next() {
return this;
}
AbstractScheduledEventExecutor inherits from AbstractEventExecutor, which internally uses a PriorityQueue to store ScheduledFutureTask containing timed tasks, so as to realize the function of timed tasks:
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;
Next is SingleThreadEventExecutor. As can be seen from the name, SingleThreadEventExecutor uses a single thread to execute submitted tasks. SingleThreadEventExecutor provides a default task size of pending execution tasks: DEFAULT_MAX_PENDING_EXECUTOR_TASKS, and also defines several states of task execution:
private static final int ST_NOT_STARTED = 1;
private static final int ST_STARTED = 2;
private static final int ST_SHUTTING_DOWN = 3;
private static final int ST_SHUTDOWN = 4;
private static final int ST_TERMINATED = 5;
As mentioned earlier, there is a unique inEventLoop method in EventExecutor to determine whether a given thread is in eventLoop. In SingleThreadEventExecutor, let's take a look at the specific implementation:
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
Specifically, it is to determine whether the given thread and the thread attribute defined in SingleThreadEventExecutor are the same thread. The thread in SingleThreadEventExecutor is defined as follows:
This thread is initialized in the doStartThread method:
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
So this thread is the thread of task execution, that is, the thread used to execute the task in the executor.
Let's take a look at the very critical execute method:
private void execute(Runnable task, boolean immediate) {
boolean inEventLoop = inEventLoop();
addTask(task);
if (!inEventLoop) {
startThread();
This method first adds the task to the task queue, and then calls startThread to start the thread to execute the task.
Finally, let's take a look at DefaultEventExecutor, the default implementation in netty:
public final class DefaultEventExecutor extends SingleThreadEventExecutor
DefaultEventExecutor inherits from SingleThreadEventExecutor, in this class, it defines how the run method is implemented:
protected void run() {
for (;;) {
Runnable task = takeTask();
if (task != null) {
task.run();
updateLastExecutionTime();
}
if (confirmShutdown()) {
break;
}
}
}
In SingleThreadEventExecutor, we will add the task to the task queue. In the run method, we will take out the corresponding task from the task queue, and then call the run method of the task to execute.
Summarize
DefaultEventExecutorGroup inherits MultithreadEventExecutorGroup, which actually calls SingleThreadEventExecutor in MultithreadEventExecutorGroup to perform specific tasks.
This article has been included in http://www.flydean.com/05-1-netty-event…entexecutorgroup/
The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!
Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。