源码分析socketChannel,为什么register前需要调用selector的weakup方法
直接上源码,查看 register 的实现。我们查看 register 的实现会直接跟进抽象类 SelectableChannel 中:
调用了本身的另一个 register 方法:
该实现为抽象方法,我们直接向下查找 SelectableChannel 子类,查看其实现。因为 SocketChannel 为 ServerSocketChannel 的 accept 方法返回的,跟进 accept 方法,查看其返回了哪个实现类的实例:
可以看到,accept 方法返回的是 SocketChannelImpl 的实例,直接跟进 SocketChannelImpl ,发现并没有 register 方法的实现。说明 register 方法在其某个父类中已经被实现了,我们向上寻找最近的实现了 register 方法的父类,直接查看 UML 图:
可以看到,距离 SocketChannel 最近的持有 register 方法的父类为 AbstractSelectableChannel ,跟进去:
findKey 为 selector 中 key 已存在的情况,key 不存在时,依靠调用 AbstractSelector 的 register 方法获取 key。可以看出,selector.register(channel) 比较符合通常的逻辑,而这里用 channel 去 register( selector ) 本身在新获取 key 时也是这样实现的。暴露给我们的方法为 channel 去 register( selector ) 的原因在这里也很明显了。channel 在设计时是可以对应多个 selector 的,也就是说同一个 channel 可以在多个 selector 上注册自己感兴趣的事件。因此每次在注册事件时,register 方法会检查在该 selector 上,本 channel 是否已经注册过了,如果注册过了,直接通过 findKey 方法返回,否则才去注册。所以说在一个 selector 上,一个 channel 只能注册一次。
调用 weakup 方法的原因还没有找到,继续到 AbstractSelector 跟进。与上面的过程一致,发现 register 是在 SelectorImpl 中实现的:
而 select 方法的实现基于 lockAndDoSelect 方法,他们共用了 publicKeys 这把锁:
在 lockAndDoSelect 方法中,用 publicKeys 锁住了一个抽象方法 doSelect,doSelect 的实现不在解析,只需要知道这是一个阻塞方法,阻塞等待事件发生。
所以如果没有事件发生,锁 publicKeys 会一直被 select 方法霸占,register 方法无法获取锁会一直等待锁的释放。所以如果在调用 register 前后不调用 weakup 方法,会发生既检查不到死锁,程序又卡死在 register 方法无法向下运行的情况。
weakup 方法做的就是向事件通道写入一个字节,将 doSelect 从阻塞中唤醒,从而继续向下运行释放 publicKeys 锁,给 register 方法运行的机会。