java NIO的实现中,有不少细节点非常有学习意义的,就好比下面的这个点:
Selector的 wakeup原理是什么?是如何实现的?
wakeup()
准确来说,应该是Selector的wakeup(),即Selector的唤醒,为什么要有这个唤醒操作呢?那还得从Selector的选择方式
来说明,前文已经总结过Selector的选择方式有三种:select()、select(timeout)、selectNow()。
selectNow的选择过程是非阻塞的,与wakeup没有太大关系。
select(timeout)和select()的选择过程是阻塞的,其他线程如果想终止这个过程,就可以调用wakeup来唤醒。
wakeup的原理
既然Selector阻塞式选择因为找到感兴趣事件ready才会返回(排除超时、中断),就给它构造一个感兴趣事件ready的场景即可。下图可以比较形象的形容wakeup原理:
Selector管辖的FD(文件描述符,linux即为fd,对应一个文件,windows下对应一个句柄;每个可选择Channel在创建的时
候,就生成了与其对应的FD,Channel与FD的联系见另一篇)中包含某一个FD A,
A对数据可读事件感兴趣,当往图中漏斗端放入(写入)数据,数据会流进A,于是A有感兴趣事件ready,最终,select得到结果而返回。
wakeup在Selector中的定义如下:
public abstract Selector wakeup();
下面结合上图来追寻wakeup的实现:
linux下Selector默认实现为PollSelectorImpl,当内核版本大于2.6时,实现为EPollSelectorImpl,仅看这两者的wakeup方法,代码似乎完全一样:
public Selector wakeup() {
synchronized (interruptLock) {
if (!interruptTriggered) {
pollWrapper.interrupt();
interruptTriggered = true;
}
}
return this;
}
window下Selector的实现为WindowsSelectorImpl,其wakeup实现如下:
public Selector wakeup() {
synchronized (interruptLock) {
if (!interruptTriggered) {
setWakeupSocket();
interruptTriggered = true;
}
}
return this;
}
其中interruptTriggered为中断已触发标志,当pollWrapper.interrupt()之后,该标志即为true了;得益于这个标志,连续两次wakeup,只会有一次效果。
对比上图及上述代码,其实pollWrapper.interrupt()及setWakeupSocket()就是图中的往漏斗中倒水的过程,不
管windows也好,linux也好,它们wakeup的思想是完全一致的,不同的地方就在于实现的细节了,例如上图中漏斗与通道的链接部
分,linux下是采用管道pipe来实现的,而windows下是采用两个socket之间的通讯来实现的,它们都有这样的特性:1)都有两个端,一个
是read端,一个是write端,windows中两个socket也是一个扮演read的角色,一个扮演write的角色;2)当往write端写入
数据,则read端即可以收到数据;从它们的特性可以看出,它们是能够胜任这份工作的。
如果只想理解wakeup的原理,看到这里应该差不多了,不过,下面,想继续深入一下,满足更多人的好奇心。
先看看linux下PollSelector的具体wakeup实现,分阶段来介绍:
1) 准备阶段
PollSelector在构造的时候,就将管道pipe,及wakeup专用FD给准备好,可以看一下它的实现:
PollSelectorImpl(SelectorProvider sp) {
super(sp, 1, 1);
int[] fdes = new int[2];
IOUtil.initPipe(fdes, false);
fd0 = fdes[0];
fd1 = fdes[1];
pollWrapper = new PollArrayWrapper(INIT_CAP);
pollWrapper.initInterrupt(fd0, fd1);
channelArray = new SelectionKeyImpl[INIT_CAP];
}
IOUtil.initPipe,采用系统调用pipe(int fd[2])来创建管道,fd[0]即为ready端,fd[1]即为write端。
另一个需要关注的点就是pollWrapper.initInterrupt(fd0, fd1),先看一下它的实现:
void initInterrupt(int fd0, int fd1) {
interruptFD = fd1;
putDescriptor(0, fd0);
putEventOps(0, POLLIN);
putReventOps(0, 0);
}
以看到,initInterrupt在准备wakeup专用FD,因为fd0是read端fd,fd1是write端fd:
interruptFD被初始化为write端fd;
putDescriptor(0, fd0)初始化pollfd数组中的第一个pollfd,即指PollSelector关注的第一个fd,即为fd0;
putEventOps(0, POLLIN)初始化fd0对应pollfd中的events为POLLIN,即指fd0对可读事件感兴趣;
putReventOps(0, 0)只是初始化一下fd0对应的pollfd中的revents;
2) 执行阶段
有了前面的准备工作,就看PollArrayWrapper中的interrupt()实现:
public void interrupt() {
interrupt(interruptFD);
}
interrupt是native方法,它的入参interruptFD即为准备阶段管道的write端fd,对应于上图,其实就是漏斗端,因此,就是不看其实现,也知道它肯定扮演着倒水的这个动作,看其实现:
JNIEXPORT void JNICALL
Java_sun_nio_ch_PollArrayWrapper_interrupt(JNIEnv *env, jobject this, jint fd)
{
int fakebuf[1];
fakebuf[0] = 1;
if (write(fd, fakebuf, 1) < 0) {
JNU_ThrowIOExceptionWithLastError(env,
"Write to interrupt fd failed");
}
}
可以看出,interrupt(interruptFD)是往管道的write端fd1中写入一个字节(write(fd, fakebuf, 1))。
是的,只需要往fd1中写入一个字节,fd0即满足了可读事件ready,则Selector自然会因为有事件ready而中止阻塞返回。
EPollSelector与PollSelector相比,其wakeup实现就只有initInterrupt不同,它的实现如下:
void initInterrupt(int fd0, int fd1) {
outgoingInterruptFD = fd1;
incomingInterruptFD = fd0;
epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN);
}
epfd之前的篇章里已经讲过,它是通过epoll_create创建出来的epoll文件fd,epollCtl调用内核epoll_ctl实现了往epfd上添加fd0,且其感兴趣事件为可读(EPOLLIN)。
因此可以断定,EPollSelector与PollSelector的wakeup实现是一致的。
因为之前一直专注与分析linux下的Java
NIO实现,忽略了windows下的选择过程等,这里突然讲解其wakeup实现似乎很突兀,所以打算后面专门起一篇来介绍windows下的NIO实
现,这里我们只需要理解wakeup原理,甚至自己去看看其wakeup实现,应该也没什么难度。
关于wakeup,这里还有两个疑问:
为什么wakeup方法返回Selector?
windows下也是有pipe的,为什么使用socket而不是使用pipe来实现wakeup的?
也欢迎大家留下自己的想法,一起讨论。
ps. 本文为作者原创(java nio 教程
)
请大家关注黄金档
分享到:
相关推荐
java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java...
Java NIO系列教程(一) Java NIO 概述 Java NIO系列教程(二) Channel Java NIO系列教程(三) Buffer Java NIO系列教程(四) Scatter/Gather Java NIO系列教程(五) 通道之间的数据传输 Java NIO系列教程(六)...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统的I/O模型的新技术。自Java 1.4版本引入NIO后,它为Java开发者提供了更高效的数据传输方式,尤其是在处理大量并发...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java平台中用于替代标准I/O(BIO)模型的一种新机制。NIO在Java 1.4版本引入,提供了更高效的数据处理和通道通信方式,特别适用于高并发、大数据...
Java NIO 深入探讨了 1.4 版的 I/O 新特性,并告诉您如何使用这些特性来极大地提升您所写的 Java 代码的执行效率。这本小册子就程序员所面临的有代表性的 I/O 问题作了详尽阐述,并讲解了 如何才能充分利用新的 I/O ...
Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。 Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,...
### Java NIO 处理超大数据文件的知识点详解 #### 一、Java NIO简介 Java NIO(New IO)是Java平台上的新输入/输出流API,它提供了与传统IO(即Java IO)不同的数据处理方式。NIO在Java 1.4版本引入,并在后续版本...
Java NIO(New Input/Output)是Java标准库提供的一种I/O模型,它与传统的 Blocking I/O(IO)相比,提供了更加高效的数据传输方式。在Java NIO中,"新"主要体现在非阻塞和多路复用这两个特性上,这使得NIO更适合于...
本文将深入探讨Java NIO中的Selector机制,并通过源码分析来理解其实现原理。 Selector机制是Java NIO中的核心组件,它允许单线程同时监控多个通道(Channels)的状态变化,例如连接就绪、数据可读或可写等。这种...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java从1.4版本开始引入的一种新的I/O模型,它为Java应用程序提供了更高效的数据传输方式。传统的Java I/O模型(BIO)在处理大量并发连接时效率较...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统I/O模型的新...通过分析和运行这个示例,开发者可以更深入地理解Java NIO的工作原理,并能更好地运用到实际项目中。
java侧起server(NioUdpServer1.java),基于Java Nio的selector 阻塞等候,一个android app(NioUdpClient1文件夹)和一个java程序(UI.java)作为两个client分别向该server发数据,server收到后分别打印收到的消息...
Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统I/O模型的新技术。在Java 1.4版本中引入,NIO提供了一种全新的I/O编程方式,使得Java开发者能够更高效地处理I/O操作...
Java NIO(New IO)是Java 1.4版本引入的一个新API,全称为Non-blocking Input/Output,它提供了一种不同于传统IO的编程模型,传统IO基于块I/O,而NIO则基于通道(Channel)和缓冲区(Buffer)进行数据传输。NIO的...
Java NIO(New Input/Output)是Java标准库中提供的一种I/O模型,与传统的 Blocking I/O(同步阻塞I/O)相对。NIO在Java 1.4版本引入,其设计目标是提供一种更高效、更灵活的I/O操作方式,特别适合处理大量并发连接...
基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现...