Java NIO的wakeup剖析
java NIO的實現(xiàn)中,有不少細節(jié)點非常有學習意義的,就好比下面的這個點:
Selector的 wakeup原理是什么?是如何實現(xiàn)的?
wakeup()
準確來說,應(yīng)該是Selector的wakeup(),即Selector的喚醒,為什么要有這個喚醒操作呢?那還得從Selector的選擇方式 來說明,前文已經(jīng)總結(jié)過Selector的選擇方式有三種:select()、select(timeout)、selectNow()。
selectNow的選擇過程是非阻塞的,與wakeup沒有太大關(guān)系。
select(timeout)和select()的選擇過程是阻塞的,其他線程如果想終止這個過程,就可以調(diào)用wakeup來喚醒。
wakeup的原理
既然Selector阻塞式選擇因為找到感興趣事件ready才會返回(排除超時、中斷),就給它構(gòu)造一個感興趣事件ready的場景即可。下圖可以比較形象的形容wakeup原理:
Selector管轄的FD(文件描述符,linux即為fd,對應(yīng)一個文件,windows下對應(yīng)一個句柄;每個可選擇Channel在創(chuàng)建的時 候,就生成了與其對應(yīng)的FD,Channel與FD的聯(lián)系見另一篇)中包含某一個FD A, A對數(shù)據(jù)可讀事件感興趣,當往圖中漏斗端放入(寫入)數(shù)據(jù),數(shù)據(jù)會流進A,于是A有感興趣事件ready,最終,select得到結(jié)果而返回。
wakeup在Selector中的定義如下:
- public abstract Selector wakeup();
下面結(jié)合上圖來追尋wakeup的實現(xiàn):
linux下Selector默認實現(xiàn)為PollSelectorImpl,當內(nèi)核版本大于2.6時,實現(xiàn)為EPollSelectorImpl,僅看這兩者的wakeup方法,代碼似乎完全一樣:
- public Selector wakeup() {
- synchronized (interruptLock) {
- if (!interruptTriggered) {
- pollWrapper.interrupt();
- interruptTriggered = true;
- }
- }
- return this;
- }
window下Selector的實現(xiàn)為WindowsSelectorImpl,其wakeup實現(xiàn)如下:
- public Selector wakeup() {
- synchronized (interruptLock) {
- if (!interruptTriggered) {
- setWakeupSocket();
- interruptTriggered = true;
- }
- }
- return this;
- }
其中interruptTriggered為中斷已觸發(fā)標志,當pollWrapper.interrupt()之后,該標志即為true了;得益于這個標志,連續(xù)兩次wakeup,只會有一次效果。
對比上圖及上述代碼,其實pollWrapper.interrupt()及setWakeupSocket()就是圖中的往漏斗中倒水的過程,不 管windows也好,linux也好,它們wakeup的思想是完全一致的,不同的地方就在于實現(xiàn)的細節(jié)了,例如上圖中漏斗與通道的鏈接部 分,linux下是采用管道pipe來實現(xiàn)的,而windows下是采用兩個socket之間的通訊來實現(xiàn)的,它們都有這樣的特性:
1)都有兩個端,一個 是read端,一個是write端,windows中兩個socket也是一個扮演read的角色,一個扮演write的角色;
2)當往write端寫入 數(shù)據(jù),則read端即可以收到數(shù)據(jù);從它們的特性可以看出,它們是能夠勝任這份工作的。
如果只想理解wakeup的原理,看到這里應(yīng)該差不多了,不過,下面,想繼續(xù)深入一下,滿足更多人的好奇心。
先看看linux下PollSelector的具體wakeup實現(xiàn),分階段來介紹:
1) 準備階段
PollSelector在構(gòu)造的時候,就將管道pipe,及wakeup專用FD給準備好,可以看一下它的實現(xiàn):
- PollSelectorImpl(SelectorProvider sp) {
- super(sp, 1, 1);
- int[] fdes = new int[2];
- IOUtil.initPipe(fdes, false);
- fd0 = fdes[0];
- fd1 = fdes[1];
- pollWrapper = new PollArrayWrapper(INIT_CAP);
- pollWrapper.initInterrupt(fd0, fd1);
- channelArray = new SelectionKeyImpl[INIT_CAP];
- }
IOUtil.initPipe,采用系統(tǒng)調(diào)用pipe(int fd[2])來創(chuàng)建管道,fd[0]即為ready端,fd[1]即為write端。
另一個需要關(guān)注的點就是pollWrapper.initInterrupt(fd0, fd1),先看一下它的實現(xiàn):
- void initInterrupt(int fd0, int fd1) {
- interruptFD = fd1;
- putDescriptor(0, fd0);
- putEventOps(0, POLLIN);
- putReventOps(0, 0);
- }
以看到,initInterrupt在準備wakeup專用FD,因為fd0是read端fd,fd1是write端fd:
interruptFD被初始化為write端fd;
putDescriptor(0, fd0)初始化pollfd數(shù)組中的***個pollfd,即指PollSelector關(guān)注的***個fd,即為fd0;
putEventOps(0, POLLIN)初始化fd0對應(yīng)pollfd中的events為POLLIN,即指fd0對可讀事件感興趣;
putReventOps(0, 0)只是初始化一下fd0對應(yīng)的pollfd中的revents;
2) 執(zhí)行階段
有了前面的準備工作,就看PollArrayWrapper中的interrupt()實現(xiàn):
- public void interrupt() {
- interrupt(interruptFD);
- }
interrupt是native方法,它的入?yún)nterruptFD即為準備階段管道的write端fd,對應(yīng)于上圖,其實就是漏斗端,因此,就是不看其實現(xiàn),也知道它肯定扮演著倒水的這個動作,看其實現(xiàn):
- JNIEXPORT void JNICALL
- Java_sun_nio_ch_PollArrayWrapper_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");
- }
- }
可以看出,interrupt(interruptFD)是往管道的write端fd1中寫入一個字節(jié)(write(fd, fakebuf, 1))。
是的,只需要往fd1中寫入一個字節(jié),fd0即滿足了可讀事件ready,則Selector自然會因為有事件ready而中止阻塞返回。
EPollSelector與PollSelector相比,其wakeup實現(xiàn)就只有initInterrupt不同,它的實現(xiàn)如下:
- void initInterrupt(int fd0, int fd1) {
- outgoingInterruptFD = fd1;
- incomingInterruptFD = fd0;
- epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN);
- }
epfd之前的篇章里已經(jīng)講過,它是通過epoll_create創(chuàng)建出來的epoll文件fd,epollCtl調(diào)用內(nèi)核epoll_ctl實現(xiàn)了往epfd上添加fd0,且其感興趣事件為可讀(EPOLLIN)。
因此可以斷定,EPollSelector與PollSelector的wakeup實現(xiàn)是一致的。
因為之前一直專注與分析linux下的Java NIO實現(xiàn),忽略了windows下的選擇過程等,這里突然講解其wakeup實現(xiàn)似乎很突兀,所以打算后面專門起一篇來介紹windows下的NIO實 現(xiàn),這里我們只需要理解wakeup原理,甚至自己去看看其wakeup實現(xiàn),應(yīng)該也沒什么難度。
關(guān)于wakeup,這里還有兩個疑問:
為什么wakeup方法返回Selector?
windows下也是有pipe的,為什么使用socket而不是使用pipe來實現(xiàn)wakeup的?
也歡迎大家留下自己的想法,一起討論。
原文鏈接:http://goldendoc.iteye.com/blog/1152079
【編輯推薦】