我的位置:首页 > 综合>其它

Netty源码分析--Channel注册(中)(六)

时间:2019-07-03 10:31:55 来源:互联网 作者: 神秘的大神 字体:

        接上一篇,我们继续看

    

    不知道大家第一次看这段代码的时候有没有一脸懵逼,反正我是一脸懵,为什么这个if else 最终都是调用的register0方法,都是一样的。

    其实这里就是为什么Netty是线程安全的根本原因。

    我们先看下 eventLoop.inEventLoop() 方法

    

   

      第一张图传入了 当前的 线程, 第二个图 判断了 当前这个NioEventLoop中的Thread 是不是和当前线程相等, 如果相等返回true, 相反就是false.

      我们debug 看一下

    发现NioEventLoop中的Thread 当前并没有赋值, 值是null,所以返回false.

   那么代码也就进入到了

   

   这里其实也容易漏看,其实这里不只是启动一个子线程来执行register0, 其实在这之前还做了好多时间。

   我们进入eventLoop的execute()方法,惊喜不。。。

    

    inEventLoop的值肯定是false,  然后执行addTask(task),把当前这个任务(register0)加入到队列中,看下这个队列

   

   

   这个队列是一个LinkedBlockingQueue. 

   继续

   

   

    这就是举世闻名的CAS无锁技术,当然不了解CAS的自行百度。这里我想说的是,大家可以学习一下Netty这种写法。

    

 

          

       CAS方式原子性更新state字段的值,这里的state一定要使用volatile修饰,这个关键字不太了解的,也自行百度。

       回到  startThread() 方法, 先检查一下Thread 是否已经启动, 如果没有启动,就把state原子性改成 启动状态 ,如果在启动过程中出现异常,则再次把state原子性改成 未启动状态。

       继续进入 doStartThread() 方法

       

       先是一个断言来保证thread一定是null, 然后启动一个子线程,并把当前这个子线程 赋值给了当前的 这个NioEventLoop 中的 thread 成员变量。 ok ,到现在为止,NioEventLoop 中的唯一线程确定。

      

      从这里我们进入run() 方法

      

      我们发现进入到了一个死循环, 然后里面有一个switch分支,我们来看下里面的策略计算方法。

      在说这个之前我们再来一起看一个NIO中多路复用器的API

     

       不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。

       同时这个方法会清除wakeup()方法的效果。

    

       此方法执行阻塞的 selection operation 。 只有在选择了至少一个通道之后,才会返回此选择器的wakeup方法,当前线程被中断,或给定的超时期限到期,以先到者为准。

       此方法不提供实时保证:它调用超时,就像调用Object.wait(long)方法一样。

    

      如果另一个线程在调用select()select(long)方法时被阻止,则该调用将立即返回。 如果当前没有选择操作,则下一次调用这些方法之一将立即返回,除非在此期间调用selectNow()方法。 无论如何,该调用返回的值可能不为零。 的后续调用select()select(long)除非此方法在此期间再次调用的方法将阻塞如常。 在两次连续的选择操作之间多次调用此方法与调用它一样具有相同的效果

     

    好了,了解了这些我们继续看,

    

    先检查是否有待处理的task,如果有那么就非阻塞的检查一下是否有新的channel被注册,然后返回channel注册的数量,可能是0, 如果没有task,则返回 - 1

    我们发现如果有task,那么这么switch就直接跳出了。如果返回 - 1 ,就执行 select(wakenUp.getAndSet(false))

    我们先看下没有task的情况吧。先大概读一下这一大段注释

   

   大概的意思是说:

   在调用选择器唤醒方法,之前,先确定wakenUp的值,以减少唤醒负载,因为唤醒选择器是一个耗时的操作。  但是不能把warkup设置true太早,将会触发竞争。

   1、选择器在wakenUp属性更新为false和选择操作之间被唤醒

   2、选择器在选择操作和获取wakenUp属性之间

   在第一种情况下,当wakenUp属性更新为true,接下来的选择操作就会立刻被唤醒, 直到在下一次循环中wakenUp属性更新为false,wakenUp.compareAndSet(false, true)  ,将会失败,同时引起下一次不必要的选择操作阻塞, 怎么这句话呢(自己的理解)。

   我们看一下这个方法 select(wakenUp.getAndSet(false))

   首先我们假如入参当前是false, 也就是 oldWakenUp = false

   

    那么再假如当前是有task待处理的,那么也就是说  hasTasks() && wakenUp.compareAndSet(false, true)  == true , 那么将执行selectNow(), 也就是当前时间到上一次select操作的期间内是否有channel注册进来。

    然后break,接下来

   wakeUp 刚刚被CAS 成 true ,所以这里会执行wakeup操作,也就意味着下一次select操作将会被立即返回。

   接下来就是去处理task 和 新接入的客户端或者读写操作了(一会再说这个)。 

   因为是死循环,我们继续回来,又到了 

   

    这次的wakeUp 变成了true, 并且把状态置为false, 那么也就是说  oldWakenUp = true 

    

      这里不管有没有任务,都会立即返回,因为我们之前执行了selector.wakeup(),这里我自己猜测可能是因为处理读写和任务用掉了很长时间,所以这里直接就检查当前是会有channel已经注册进来已经在等待了。

      如果有的话,直接break.去执行。 

      当然如果之前没有 selector.wakeup() 过,那么将会执行 1s 的时间,看着1s 内是否有新的channel进来。

      

        

       继续看,通过这两段我们发现如果循环超时了,那么将会break掉。

      

 

    

      通过这两段我们发现,当循环512次之后,那么将会重建Selector

       

      这里其实是因为JDK的BUG导致的,会把CPU飚到100%

      整个重建的过程其实就是,创建新的selector,把老的上面的 SelectionKey 都注册到新的selector上,然后将老的selector关闭掉,具体的内容就不一起看了。