【转】Selector.wakeup实现注记版权信息:原文地址:http://www.blogjava.net/killme2008/archive/2010/10/22/
【转】Selector.wakeup实现注记
版权信息:
原文地址:http://www.blogjava.net/killme2008/archive/2010/10/22/335861.html
原文作者:dennis.zane
?
?
NIO中的Selector封装了底层的系统调用,其中wakeup用于唤醒阻塞在select方法上的线程,它的实现很简单,在linux上就是创建一个管道并加入poll的fd集合,wakeup就是往管道里写一个字节,那么阻塞的poll方法有数据可读就立即返回。证明这一点很简单,strace即可知道:
?
public?class?SelectorTest?{
????public?static?void?main(String[]?args)?throws?Exception?{
????????Selector?selector?=?Selector.open();
????????selector.wakeup();
????}
}
???? 使用strace调用,只关心write的系统调用
sudo?strace?-f?-e?write?java SelectorTest
???? 输出:
Process?29181?attached
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?class?SelectorTest?{
????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?29303?attached
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?class?SelectorTest?{
????public?static?void?main(String[]?args)?throws?Exception?{
????????Selector?selector?=?Selector.open();
????????selector.wakeup();
????????selector.wakeup();
????????selector.wakeup();
????}
}
??
?? 输出:
Process?29331?attached
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方法:
public??void?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充分利用对了解这些细节很有帮助。