接上一篇,咱们继续看安全
不知道你们第一次看这段代码的时候有没有一脸懵逼,反正我是一脸懵,为何这个if else 最终都是调用的register0方法,都是同样的。oop
其实这里就是为何Netty是线程安全的根本缘由。学习
咱们先看下 eventLoop.inEventLoop() 方法spa
第一张图传入了 当前的 线程, 第二个图 判断了 当前这个NioEventLoop中的Thread 是否是和当前线程相等, 若是相等返回true, 相反就是false.线程
咱们debug 看一下debug
发现NioEventLoop中的Thread 当前并无赋值, 值是null,因此返回false.3d
那么代码也就进入到了code
这里其实也容易漏看,其实这里不仅是启动一个子线程来执行register0, 其实在这以前还作了好多时间。blog
咱们进入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太早,将会触发竞争。
一、选择器在wakenUp属性更新为false和选择操做之间被唤醒
二、选择器在选择操做和获取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关闭掉,具体的内容就不一块儿看了。