java nio源码_NIO源码详解 - 沉稳2018的个人空间 - OSCHINA - 中文开源技术交流社区...

阻塞io和无阻塞io:

阻塞io是指jdk1.4之前版本面向流的io,服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒 绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。

当并发量大,而后端服务或客户端处理数据慢时就会产生产生大量线程处于等待中,即上述的阻塞。

302ebdec47f4c991d663a2a6abd0b128.png

无阻塞io是使用单线程或者只使用少量的多线程,每个连接共用一个线程,当处于等待(没有事件)的时候线程资源可以释放出来处理别的请求,通过事件驱动模型当有accept/read/write等事件发生后通知(唤醒)主线程分配资源来处理相关事件。java.nio.channels.Selector就是在该模型中事件的观察者,可以将多个SocketChannel的事件注册到一个Selector上,当没有事件发生时Selector处于阻塞状态,当SocketChannel有accept/read/write等事件发生时唤醒Selector。

51499b53c1d6a8862d92bbd83955053c.png

这个Selector是使用了单线程模型,主要用来描述事件驱动模型,要优化性能需要一个好的线程模型来使用,目前比较好的nio框架有Netty,apache的mina等。线程模型这块后面再分享,这里重点研究Selector的阻塞和唤醒原理。

退出阻塞的方式有:register在selector上的socketChannel处于就绪状态(放在pollArray中的socketChannel的FD就绪) 或者 第1节中放在pollArray中的wakeupSourceFd就绪。前者(socketChannel)就绪唤醒应证了文章开始的阻塞->事件驱动->唤醒的过程,后者(wakeupSourceFd)就是下面要看的主动wakeup。

这里创建了一个管道pipe,并对pipe的source端的POLLIN事件感兴趣,addWakeupSocket方法将source的POLLIN事件标识为感兴趣的,当sink端有数据写入时,source对应的文件描述描wakeupSourceFd就会处于就绪状态。(事实上windows就是通过向管道中写数据来唤醒阻塞的选择器的)从以上代码可以看出:通道的打开实际上是构造了一个SelectorImpl对象

subSelector.poll() 是select的核心,由native函数poll0实现,readFds、writeFds 和exceptFds数组用来保存底层select的结果,数组的第一个位置都是存放发生事件的socket的总数,其余位置存放发生事件的socket句柄fd

ffd1ce8e430cf0707a39700dc458aa83.png

图像详解

1,Selector.open()

e734cc50f4ab16a83529beed0a5f2f9b.png

Pipe.open()打开一个管道 拿到wakeupSourceFd和wakeupSinkFd两个文件描述符;把唤醒端的文件描述符(wakeupSourceFd)放到pollWrapper里; 上图中最下面那部分创建pipe的过程,windows下的实现是创建两个本地的socketChannel,然后连接(链接的过程通过写一个随机long做两个socket的链接校验),两个socketChannel分别实现了管道的source与sink端。

source端由前面提到的WindowsSelectorImpl放到了pollWrapper中(pollWrapper.addWakeupSocket(wakeupSourceFd, 0))

00e261624bc7188c093dcf16a8849e8a.png

pollWrapper用Unsafe类申请一块物理内存,存放注册时的socket句柄fdVal和event的数据结构pollfd,其中pollfd共8字节,0~3字节保存socket句柄,4~7字节保存event

ce73622058d5f90381e0bbbe7f98c97f.png

先了解一下Unsafe的基本操作

//分配var1字节大小的内存,返回起始地址偏移量public native longallocateMemory(longvar1);

//重新给var1起始地址的内存分配长度为var3字节大小的内存,返回新的内存起始地址偏移量public native longreallocateMemory(longvar1, longvar3);

//释放起始地址为var1的内存public native voidfreeMemory(longvar1);

pollWrapper申请了一个64个字节的对外内存空间,address为内存空间的开始地址

PollArrayWrapper pollWrapper = newPollArrayWrapper(8);

PollArrayWrapper(intvar1) { intvar2 = var1 * SIZE_POLLFD;

this.pollArray = newAllocatedNativeObject(var2, true);

this.pollArrayAddress = this.pollArray.address(); this.size = var1; }

protectedNativeObject(intvar1, booleanvar2) {

if(!var2) {

this.allocationAddress = unsafe.allocateMemory((long)var1);

this.address = this.allocationAddress;

} else{

intvar3 = pageSize();

longvar4 = unsafe.allocateMemory((long)(var1 + var3));

this.allocationAddress = var4;

this.address = var4 + (long)var3 - (var4 & (long)(var3 - 1));

}

}

pollWrapper.addWakeupSocket(wakeupSourceFd, 0),把唤醒端的文件描述符(wakeupSourceFd)放到pollWrapper里,本次操作pollfd会使用6个字节

voidaddWakeupSocket(intvar1, intvar2) {

this.putDescriptor(var2, var1);

this.putEventOps(var2, Net.POLLIN);

}

voidputDescriptor(intvar1, intvar2) {

this.pollArray.putInt(SIZE_POLLFD * var1 + 0, var2);

}

voidputEventOps(intvar1, intvar2) {

this.pollArray.putShort(SIZE_POLLFD * var1 + 4, (short)var2);

}

实际上pollfd的结构

43ab85bd0faacd3c4cea54050a47d8ed.png

85f88c9de99c9badc5cd72721f43f4ae.png

2,Channel.Register()

c449ecefcd56cfae8e32f1b85f9da2ba.png

3,Selector.select()

4b3ed11eb35efbdf296799b0c53e90ca.png

dff66a5f18286cb3bef0e32b417ae53f.png

4,SelectionKey.cancel()

上图浅蓝色部分

网上找的一些辅助理解图片

e7c090e5bedb0d71d3c88a9d0b3d147d.png

8c768d20ed3c6d19abf03be017e832c2.png

3fb92fd603332972d1557878f5bab00a.png

63d05a49030c313905449269e078840d.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值