版权信息:
原文地址:http://www.blogjava.net/killme2008/archive/2010/10/22/335861.html
原文作者:dennis.zane
NIO中的Selector封装了底层的系统调用,其中wakeup用于唤醒阻塞在select方法上的线程,它的实现很简单,在linux上就是创建一个管道并加入poll的fd集合,wakeup就是往管道里写一个字节,那么阻塞的poll方法有数据可读就立即返回。证明这一点很简单,strace即可知道:
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
selector.wakeup();
}
}
使用strace调用,只关心write的系统调用
输出:
Process 29182 attached
Process 29183 attached
Process 29184 attached
Process 29185 attached
Process 29186 attached
Process 29187 attached
Process 29188 attached
Process 29189 attached
Process 29190 attached
Process 29191 attached
[pid 29181 ] write( 36 , " \1 " , 1 ) = 1
Process 29191 detached
Process 29184 detached
Process 29181 detached
有的同学说了,怎么证明这个write是wakeup方法调用的,而不是其他方法呢,这个很好证明,我们多调用几次:
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
selector.wakeup();
selector.selectNow();
selector.wakeup();
selector.selectNow();
selector.wakeup();
}
}
修改程序调用三次wakeup,心细的朋友肯定注意到我们还调用了两次selectNow,这是因为在两次成功的select方法之间调用wakeup多次都只算做一次,为了显示3次write,这里就每次调用前select一下将前一次写入的字节读到,同样执行上面的strace调用,输出:
Process 29304 attached
Process 29305 attached
Process 29306 attached
Process 29307 attached
Process 29308 attached
Process 29309 attached
Process 29310 attached
Process 29311 attached
Process 29312 attached
Process 29313 attached
[pid 29303 ] write( 36 , " \1 " , 1 ) = 1
[pid 29303 ] write( 36 , " \1 " , 1 ) = 1
[pid 29303 ] write( 36 , " \1 " , 1 ) = 1
Process 29313 detached
Process 29309 detached
Process 29306 detached
Process 29303 detached
果然是3次write的系统调用,都是写入一个字节,如果我们去掉selectNow,那么三次wakeup还是等于一次:
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
selector.wakeup();
selector.wakeup();
selector.wakeup();
}
}
输出:
Process 29332 attached
Process 29333 attached
Process 29334 attached
Process 29335 attached
Process 29336 attached
Process 29337 attached
Process 29338 attached
Process 29339 attached
Process 29340 attached
Process 29341 attached
[pid 29331 ] write( 36 , " \1 " , 1 ) = 1
Process 29341 detached
Process 29337 detached
Process 29334 detached
Process 29331 detached
wakeup方法的API说明没有欺骗我们。wakeup方法的API还告诉我们,如果当前Selector没有阻塞在select方法上,那么本次wakeup调用会在下一次select阻塞的时候生效,这个道理很简单,wakeup方法写入一个字节,下次poll等待的时候立即发现可读并返回,因此不会阻塞。
具体到源码级别,在linux平台上的wakeup方法其实调用了pipe创建了管道,wakeup调用了EPollArrayWrapper的interrupt方法:
{
interrupt(outgoingInterruptFD);
}
实际调用的是interrupt(fd)的native方法,查看EPollArrayWrapper.c可见清晰的write系统调用:
JNIEXPORT void JNICALL
Java_sun_nio_ch_EPollArrayWrapper_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 " );
}
}
写入一个字节的fakebuf。有朋友问起这个问题,写个注记在此。strace充分利用对了解这些细节很有帮助。