版权信息:
原文地址: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充分利用对了解这些细节很有帮助。