Selector.wakeup實(shí)現(xiàn)注記
Posted on 2010-10-22 10:35 dennis 閱讀(6228) 評(píng)論(3) 編輯 收藏 所屬分類: java 、源碼解讀NIO中的Selector封裝了底層的系統(tǒng)調(diào)用,其中wakeup用于喚醒阻塞在select方法上的線程,它的實(shí)現(xiàn)很簡(jiǎn)單,在linux上就是創(chuàng)建一個(gè)管道并加入poll的fd集合,wakeup就是往管道里寫一個(gè)字節(jié),那么阻塞的poll方法有數(shù)據(jù)可讀就立即返回。證明這一點(diǎn)很簡(jiǎn)單,strace即可知道:
public class SelectorTest {
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
selector.wakeup();
}
}
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
selector.wakeup();
}
}
使用strace調(diào)用,只關(guān)心write的系統(tǒng)調(diào)用
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
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
有的同學(xué)說(shuō)了,怎么證明這個(gè)write是wakeup方法調(diào)用的,而不是其他方法呢,這個(gè)很好證明,我們多調(diào)用幾次:
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();
}
}
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
selector.wakeup();
selector.selectNow();
selector.wakeup();
selector.selectNow();
selector.wakeup();
}
}
修改程序調(diào)用三次wakeup,心細(xì)的朋友肯定注意到我們還調(diào)用了兩次selectNow,這是因?yàn)樵趦纱纬晒Φ膕elect方法之間調(diào)用wakeup多次都只算做一次,為了顯示3次write,這里就每次調(diào)用前select一下將前一次寫入的字節(jié)讀到,同樣執(zhí)行上面的strace調(diào)用,輸出:
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
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的系統(tǒng)調(diào)用,都是寫入一個(gè)字節(jié),如果我們?nèi)サ魋electNow,那么三次wakeup還是等于一次:
public class SelectorTest {
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
selector.wakeup();
selector.wakeup();
selector.wakeup();
}
}
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
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說(shuō)明沒有欺騙我們。wakeup方法的API還告訴我們,如果當(dāng)前Selector沒有阻塞在select方法上,那么本次wakeup調(diào)用會(huì)在下一次select阻塞的時(shí)候生效,這個(gè)道理很簡(jiǎn)單,wakeup方法寫入一個(gè)字節(jié),下次poll等待的時(shí)候立即發(fā)現(xiàn)可讀并返回,因此不會(huì)阻塞。
具體到源碼級(jí)別,在linux平臺(tái)上的wakeup方法其實(shí)調(diào)用了pipe創(chuàng)建了管道,wakeup調(diào)用了EPollArrayWrapper的interrupt方法:
public void interrupt()
{
interrupt(outgoingInterruptFD);
}
{
interrupt(outgoingInterruptFD);
}
實(shí)際調(diào)用的是interrupt(fd)的native方法,查看EPollArrayWrapper.c可見清晰的write系統(tǒng)調(diào)用:
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");
}
}