因为在Thread.interrupt()方法中
public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag // 上面设置完中断标志位后,会调用当前线程的blocker的interrupt方法 b.interrupt(this); return; } } interrupt0(); }而在sun.nio.ch.EPollSelectorImpl#doSelect方法中,开始poll之前会调用begin方法
protected final void begin() { if (interruptor == null) { // 新建一个interruptor interruptor = new Interruptible() { public void interrupt(Thread ignore) { // 此时Thread.interrupt()中的blocker就是这个匿名内部类,也就是调用的这个interrupt方法 AbstractSelector.this.wakeup(); }}; } // 设置当前线程的interruptor AbstractInterruptibleChannel.blockedOn(interruptor); Thread me = Thread.currentThread(); if (me.isInterrupted()) interruptor.interrupt(me); }所以Thread.interrupt()会调用到EPollSelectorImpl#wakeup方法,也就可以起到中断select的作用。
什么时候清除中断标志?可以不止一次的中断select,为了实现这个功能,每次在中断之后斗湖清除相关的中断标志。在sun.nio.ch.EPollSelectorImpl#doSelect方法中pollWrapper.poll完成之后
int poll(long timeout) throws IOException { updateRegistrations(); updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd); for (int i=0; i<updated; i++) { // epollWait返回之后会判断有没有中断文件描述符 if (getDescriptor(i) == incomingInterruptFD) { // 设置中断的文件描述符处于返回的pollArray的index interruptedIndex = i; interrupted = true; break; } } return updated; } protected int doSelect(long timeout) throws IOException { // 省略中间代码... pollWrapper.poll(timeout); // // 省略中间代码... // 如果 if (pollWrapper.interrupted()) { // Clear the wakeup pipe // 将返回的中断文件描述符的IO事件清空,也就是用户不会读取到用做中断的文件描述符 pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0); synchronized (interruptLock) { // sun.nio.ch.EPollArrayWrapper#interrupted设置为false,可再次中断 pollWrapper.clearInterrupted(); // 读取用以中断的文件描述符上所有的数据 IOUtil.drain(fd0); interruptTriggered = false; } } return numKeysUpdated; } 总结到目前为止已经看清了Java对应linux中epoll相关api的封装
// 创建epoll文件描述符 // 对应到Java就是创建selector int epoll_create(int size); // 打开一个网络通讯端口,也就是创建一个socket,创建返回一个文件描述符 // 对应到Java就是创建一个socketChannel int socket(int domain, int type, int protocol); // 将socket对应的文件描述符和ip:port绑定在一起 // 对应于Java中绑定ip:port int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 表明socket对应的文件描述符处于监听状态,并且最多允许有backlog个客户端处于连接等待状态 // 对应于Java中bind中调用listen方法 int listen(int sockfd, int backlog); // 控制某个文件描述符上的事件:add、update、delete事件 // 对应于Java中调用select过程中添加每个channel关注的事件 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // 等待监控的所有描述符有事件发生 // 对应于Java中select的时候等待有IO事件发生 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)