EventLoop

任务调度

ScheduledExecutorService的实现具有局限性,作为线程池的管理的一部分,将会有额外的线程创建。如果有大量任务被紧凑地调度,那么这将成为一个瓶颈。Netty通过Channel的EventLoop实现任务调度解决了这一问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

Channel ch = ...;
ScheduledFuture<?> future = ch.eventLoop().schedule(
    new Runnable() {
        @Override
        public void run(){
            sout("60 seconds later");
        }
    }, 60, TimeUnit.SECONDS);
)

如果要调度任务以隔60秒执行一次,请使用scheduleAtFixedRate(),代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

Channel ch = ...;
ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(
    new Runnable() {
        @Override
        public void run(){
            sout("Run every 60 seconds");
        }
    }, 60, 60, TimeUnit.SECONDS);
)

线程管理

Netty线程模型的卓越性取决于对于当前执行的Thread的身份的确认,也就是说,确定它是否是分配给当前Channel以及它的EventLoop的那一个线程。

(不是很理解这句话,什么叫做多当前执行的Thread的身份的确认,这个确认又有什么意义)

如果当前调用线程正是支撑EventLoop的线程,那么所提交的代码块将被直接执行。否则,EventLoop将调度该任务以便稍后执行,并将它放入到内部队列中。当EventLoop下次处理它的事件时,它会执行队列中的那些任务/事件。这也就解释了任何的Thread是如何与Channel直接教化而无需在ChannelHandler中进行额外的同步的。

(我的理解是这样的,你缓存了Channel,然后在一个非EventLoop线程中调用write方法,这时候这个调用会被放入到EventLoop的内部队列中,当EventLoop下次处理它的事件时,这些事件会被一起处理)

书中的图(感觉和我的理解有一点出入)

2021-08-11-21-15-13

需要注意的是,EventLoop的分配方式对ThreadLocal的使用的影响。因为一个EventLoop通常会被用于支撑多个Channel,所以多余所有相关的Channel来说,ThreadLocal都是一样的。这使得它对于实现状态跟踪来说是一个糟糕的选择。然而,在一些无状态的上下文中,它仍然可以被用来在多个Channel之间共享一些重度或者代价昂贵的对象,甚至是事件。