Selector.wakeup() 主要作用
解除阻塞在Selector.select()/select(long)上的线程,立即返回。
两次成功的select之间多次调用wakeup等价于一次调用。
如果当前没有阻塞在select上,则本次wakeup调用将作用于下一次select——“记忆”作用。
为什么要唤醒?
注册了新的channel或者事件。
channel关闭,取消注册。
优先级更高的事件触发(如定时器事件),希望及时处理。
原理
Linux上利用pipe调用创建一个管道,Windows上则是一个loopback的tcp连接。这是因为win32的管道无法加入select的fd set,将管道或者TCP连接加入select fd set。
wakeup往管道或者连接写入一个字节,阻塞的select因为有I/O事件就绪,立即返回。可见,wakeup的调用开销不可忽视。
Buffer的选择
通常情况下,操作系统的一次写操作分为两步:
- 将数据从用户空间拷贝到系统空间。
- 从系统空间往网卡写。同理,读操作也分为两步:
① 将数据从网卡拷贝到系统空间;
② 将数据从系统空间拷贝到用户空间。
对于NIO来说,缓存的使用可以使用DirectByteBuffer和HeapByteBuffer。如果使用了DirectByteBuffer,一般来说可以减少一次系统空间到用户空间的拷贝。但Buffer创建和销毁的成本更高,更不宜维护,通常会用内存池来提高性能。
如果数据量比较小的中小应用情况下,可以考虑使用heapBuffer;反之可以用directBuffer。
NIO存在的问题
使用NIO != 高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。
NIO并没有完全屏蔽平台差异,它仍然是基于各个操作系统的I/O系统实现的,差异仍然存在。使用NIO做网络编程构建事件驱动模型并不容易,陷阱重重。
推荐大家使用成熟的NIO框架,如Netty,MINA等。解决了很多NIO的陷阱,并屏蔽了操作系统的差异,有较好的性能和编程模型。
总结
最后总结一下到底NIO给我们带来了些什么:
- 事件驱动模型
- 避免多线程
- 单线程处理多任务
- 非阻塞I/O,I/O读写不再阻塞,而是返回0
- 基于block的传输,通常比基于流的传输更高效
- 更高级的IO函数,zero-copy
- IO多路复用大大提高了Java网络应用的可伸缩性和实用性
本文抛砖引玉,诠释了一些NIO的思想和设计理念以及应用场景,这只是从冰山一角。关于NIO可以谈的技术点其实还有很多,期待未来有机会和大家继续探讨。 |