1
回答
Java NIO 那些躲在角落的细节
利用AWS快速构建适用于生产的无服务器应用程序,免费试用12个月>>>   

PS.本文来自:http://www.goldendoc.org/2011/08/java-nio-small-things/
欢迎大家关注:黄金档以及她的java nio系列

java NIO的实现中,有不少细节点非常有学习意义的,就好比下面的三个点:
1) Selector的 wakeup原理是什么?是如何实现的?
2) Channel的close会做哪些事?
3) 会什么代码中经常出现begin()和end()这一对儿?

本文虽然针对这几个点做了点分析,不能算是非常深刻,要想达到通透的地步,看来还得经过实战的洗练。

1、 wakeup()

准确来说,应该是Selector的wakeup(),即Selector的唤醒,为什么要有这个唤醒操作呢?那还得从Selector的选择方式 来说明,前文已经总结过Selector的选择方式有三种:select()、select(timeout)、selectNow()。
selectNow的选择过程是非阻塞的,与wakeup没有太大关系。
select(timeout)和select()的选择过程是阻塞的,其他线程如果想终止这个过程,就可以调用wakeup来唤醒。

wakeup的原理

既然Selector阻塞式选择因为找到感兴趣事件ready才会返回(排除超时、中断),就给它构造一个感兴趣事件ready的场景即可。下图可以比较形象的形容wakeup原理:

Selector管辖的FD(文件描述符,linux即为fd,对应一个文件,windows下对应一个句柄;每个可选择Channel在创建的时 候,就生成了与其对应的FD,Channel与FD的联系见另一篇)中包含某一个FD A, A对数据可读事件感兴趣,当往图中漏斗端放入(写入)数据,数据会流进A,于是A有感兴趣事件ready,最终,select得到结果而返回。

wakeup在Selector中的定义如下:
public abstract Selector wakeup();

下面结合上图来追寻wakeup的实现:
linux下Selector默认实现为PollSelectorImpl,当内核版本大于2.6时,实现为EPollSelectorImpl,仅看这两者的wakeup方法,代码似乎完全一样:

public Selector wakeup() {
    synchronized (interruptLock) {
        if (!interruptTriggered) {
            pollWrapper.interrupt();
            interruptTriggered = true;
        }
    }
    return this;
}

window下Selector的实现为WindowsSelectorImpl,其wakeup实现如下:

public Selector wakeup() {
    synchronized (interruptLock) {
        if (!interruptTriggered) {
            setWakeupSocket();
            interruptTriggered = true;
        }
    }
    return this;
}

其中interruptTriggered为中断已触发标志,当pollWrapper.interrupt()之后,该标志即为true了;得益于这个标志,连续两次wakeup,只会有一次效果。

对比上图及上述代码,其实pollWrapper.interrupt()及setWakeupSocket()就是图中的往漏斗中倒水的过程,不 管windows也好,linux也好,它们wakeup的思想是完全一致的,不同的地方就在于实现的细节了,例如上图中漏斗与通道的链接部 分,linux下是采用管道pipe来实现的,而windows下是采用两个socket之间的通讯来实现的,它们都有这样的特性:1)都有两个端,一个 是read端,一个是write端,windows中两个socket也是一个扮演read的角色,一个扮演write的角色;2)当往write端写入 数据,则read端即可以收到数据;从它们的特性可以看出,它们是能够胜任这份工作的。

如果只想理解wakeup的原理,看到这里应该差不多了,不过,下面,想继续深入一下,满足更多人的好奇心。
先看看linux下PollSelector的具体wakeup实现,分阶段来介绍:

1) 准备阶段

PollSelector在构造的时候,就将管道pipe,及wakeup专用FD给准备好,可以看一下它的实现:

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,采用系统调用pipe(int fd[2])来创建管道,fd[0]即为ready端,fd[1]即为write端。
另一个需要关注的点就是pollWrapper.initInterrupt(fd0, fd1),先看一下它的实现:

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数组中的第一个pollfd,即指PollSelector关注的第一个fd,即为fd0;
putEventOps(0, POLLIN)初始化fd0对应pollfd中的events为POLLIN,即指fd0对可读事件感兴趣;
putReventOps(0, 0)只是初始化一下fd0对应的pollfd中的revents;

2) 执行阶段

有了前面的准备工作,就看PollArrayWrapper中的interrupt()实现:

public void interrupt() {
    interrupt(interruptFD);
}

interrupt是native方法,它的入参interruptFD即为准备阶段管道的write端fd,对应于上图,其实就是漏斗端,因此,就是不看其实现,也知道它肯定扮演着倒水的这个动作,看其实现:

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中写入一个字节(write(fd, fakebuf, 1))。
是的,只需要往fd1中写入一个字节,fd0即满足了可读事件ready,则Selector自然会因为有事件ready而中止阻塞返回。

EPollSelector与PollSelector相比,其wakeup实现就只有initInterrupt不同,它的实现如下:

void initInterrupt(int fd0, int fd1) {
    outgoingInterruptFD = fd1;
    incomingInterruptFD = fd0;
    epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN);
}

epfd之前的篇章里已经讲过,它是通过epoll_create创建出来的epoll文件fd,epollCtl调用内核epoll_ctl实现了往epfd上添加fd0,且其感兴趣事件为可读(EPOLLIN),
因此可以断定,EPollSelector与PollSelector的wakeup实现是一致的。
因为之前一直专注与分析linux下的Java NIO实现,忽略了windows下的选择过程等,这里突然讲解其wakeup实现似乎很突兀,所以打算后面专门起一篇来介绍windows下的NIO实 现,这里我们只需要理解wakeup原理,甚至自己去看看其wakeup实现,应该也没什么难度。

关于wakeup,这里还有两个疑问:
为什么wakeup方法返回Selector?
windows下也是有pipe的,为什么使用socket而不是使用pipe来实现wakeup的?

2、 close()

close()操作限于通道,而且还是实现了InterruptibleChannel接口的通道,例如FileChannel就没有close操作。
在分析close()具体实现之前,我们先得理解为什么要有close()这个操作:
一个可选择的通道,在创建之初会生成一个FileDescriptor,linux下即为fd,windows下即为句柄,这些都是系统资源,不能无限占用,当在不使用的时候,就应该将其释放,close即是完成这个工作的。
抽象类AbstractInterruptibleChannel实现了InterruptibleChannel接口,而 SelectableChannel继承自AbstractInterruptibleChannel,因此,可选择的通道同时也是可以close的。
AbstractInterruptibleChannel的close实现如下:

public final void close() throws IOException {
synchronized (closeLock) {
    if (!open)
    return;
    open = false;
    implCloseChannel();
}
}

看来具体关闭逻辑就在implCloseChannel()中了,于是再看AbstractSelectableChannel:

protected final void implCloseChannel() throws IOException {
implCloseSelectableChannel();
synchronized (keyLock) {
        int count = (keys == null) ? 0 : keys.length;
    for (int i = 0; i < count; i++) {
    SelectionKey k = keys[i];
    if (k != null)
        k.cancel();
    }
}
}

先看synchronized同步块,它将当前通道保存的SelectionKey全部cancel,意思就是说,当前通关闭了,与它相关的所有 SelectionKey都没有意义了,所以要全部取消掉,之前讲解cancel过程已经说明了,cancel操作只是将SelectionKey加入对 应选择器的cancelKeys集合中,在下次正式选择开始的时候再一一清除;
这么看来,还是应该追究一下implCloseSelectableChannel()的实现了,下面分别从ServerSocketChannel和SocketChannel实现出发:
先看ServerSocketChannelImpl,

protected void implCloseSelectableChannel() throws IOException {
synchronized (stateLock) {
    nd.preClose(fd);
    long th = thread;
    if (th != 0)
    NativeThread.signal(th);
    if (!isRegistered())
    kill();
}
}

出现了两个很奇怪的东西,看来要完全弄懂这段代码,是得好好分析一下它们了,它们是:NativeDispatcher nd和NativeThread;
如果已经对linux信号机制非常熟悉,应该很容易猜测到NativeThread.signal(th)在做什么,
是的,它在唤醒阻塞的线程th,下面我们来看看它是如何做到的:
NativeThread类非常简单,几乎全是native方法:

class NativeThread {
    static native long current();
    static native void signal(long nt);
    static native void init();
    static {
        Util.load();
        init();
    }
}

在看其本地实现:

//自定义中断信号,kill –l
#define INTERRUPT_SIGNAL (__SIGRTMAX - 2)
//自定义的信号处理函数,当前函数什么都不做
static void
nullHandler(int sig)
{
}
#endif
//NativeThread.init()的本地实现,可以看到它用到了sigaction
//sigaction用来install一个信号
JNIEXPORT void JNICALL
Java_sun_nio_ch_NativeThread_init(JNIEnv *env, jclass cl)
{
#ifdef __linux__
sigset_t ss;
// 以下这段代码是常见的信号安装过程
// 讲解这段代码的目的只是为了让大家理解NativeThread.signal
// 的工作原理,故很多细节就简单带过了
struct sigaction sa, osa;
// sa用于定制信号INTERRUPT_SIGNAL的处理方式的
// 如sa_handler = nullHandler即用来指定信号处理函数的
// 即线程收到信号时,为执行这个函数,nullHandler是个空壳
// 函数,所以它什么都不做
// 不用理解sa_flags各个标识代表什么
// sigemptyset顾名思义,它是初始化sigaction的sa_mask位
// sigaction(INTERRUPT_SIGNAL, &sa, &osa)执行后
// 如果成功,则表示INTERRUPT_SIGNAL这个信号安装成功了
// 为什么要有这个init呢,其实不用这不操作也许不会有问题
// 但因为不能确保INTERRUPT_SIGNAL没有被其他线程install
// 过,如果sa_handler对应函数不是空操作,则在使用这个信号
// 时会对当前线程有影响
    sa.sa_handler = nullHandler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    if (sigaction(INTERRUPT_SIGNAL, &sa, &osa) < 0)
    JNU_ThrowIOExceptionWithLastError(env, "sigaction");
#endif
}
 
JNIEXPORT jlong JNICALL
Java_sun_nio_ch_NativeThread_current(JNIEnv *env, jclass cl)
{
#ifdef __linux__
// pthread_self()即是获取当前线程ID,它与getpid()是不同的
// 具体细节没有研究
    return (long)pthread_self();
#else
    return -1;
#endif
}
 
JNIEXPORT void JNICALL
Java_sun_nio_ch_NativeThread_signal(JNIEnv *env, jclass cl, jlong thread)
{
#ifdef __linux__
//这个就是最关键的signal实现了,可以看到,它调用了pthread库的pthread_kill
//像thread线程发送一个INTERRUPT_SIGNAL信号,这个信号就是在init中install
//的,对应的处理函数是空函数,也就是说,往thread线程发送一个信号,如果该线程处于
//阻塞状态,则会因为受到信号而终止阻塞,而如果处于非阻塞,则无影响
    if (pthread_kill((pthread_t)thread, INTERRUPT_SIGNAL))
    JNU_ThrowIOExceptionWithLastError(env, "Thread signal failed");
#endif
}

Java的NativeThread做静态初始化时已经执行了init,也就是说INTERRUPT_SIGNAL信号已经被安装,而ServerSocketChannelImpl中的thread有两种可能值,见代码段:

try {
          begin();
          if (!isOpen())
            return null;
          thread = NativeThread.current();
          for (;;) {
              n = accept0(this.fd, newfd, isaa);
              if ((n == IOStatus.INTERRUPTED) && isOpen())
              continue;
              break;
          }
          } finally {
            thread = 0;
            end(n > 0);
            assert IOStatus.check(n);
        }

try的内部,for循环之前,thread被复制为NativeThread.current()即为当前线程id;finally时thread又被修改回0,因此在implCloseSelectableChannel才有这样一段:

if (th != 0)
        NativeThread.signal(th);

NativeThread.signal(th)通过像当前线程发送INTERRUPT_SIGNAL信号而确保th线程没有被阻塞,即如果阻塞就停止阻塞。

为了让大家更好的理解信号的安装和使用,下面写了一个小程序来说明:

#include <pthread.h>
#include <stdio.h>
#include <sys/signal.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
 
#define NUMTHREADS 3
#define INTERRUPT_SIGNAL (SIGRTMAX - 2)
 
void *threadfunc(void *parm)
{
    pthread_t             self = pthread_self();
    int                   rc;
    printf("Thread 0x%.8x %.8x entered\n", self);
    errno = 0;
    rc = sleep(30);
    if (rc != 0 && errno == EINTR) {
        printf("Thread 0x%.8x %.8x got a signal delivered to it\n",
                self);
        return NULL;
    }
    printf("Thread 0x%.8x %.8x did not get expected results! rc=%d, errno=%d\n",
            self, rc, errno);
    return NULL;
}
 
void sigroutine(int dunno) {
    printf("\nI'm doing nothing here : %d\n", dunno);
    return;
}
 
int main () {
    int                     i;
    int                     rc;
    struct sigaction        actions;
    pthread_t               threads[NUMTHREADS];
    memset(&actions, 0, sizeof(actions));
    sigemptyset(&actions.sa_mask);
    actions.sa_flags = 0;
    actions.sa_handler = sigroutine;
 
    rc = sigaction(INTERRUPT_SIGNAL,&actions,NULL);
    if(rc){
        printf("sigaction error!\n");
        exit(-1);
    }
 
    for(i = 0; i < NUMTHREADS; ++i) {
        rc = pthread_create(&threads[i], NULL, threadfunc,(void*)i);
        if(rc){
            printf("pthread_create error!\n");
            exit(-1);
        }
    }
 
    sleep(3);
    rc = pthread_kill(threads[0], INTERRUPT_SIGNAL);
    if(rc){
        printf("pthread_kill error!\n");
        exit(-1);
    }
    for(;;);
    return 1;
}

编译命令:gcc -lpthread –o signal_test.out
输出样本:
Thread 0xb77bcb70 00016088 entered
Thread 0xb6fbbb70 00000000 entered
Thread 0xb67bab70 00000000 entered

I’m doing nothing here : 62
Thread 0xb77bcb70 00016088 got a signal delivered to it
Thread 0xb6fbbb70 00000000 did not get expected results! rc=0, errno=0
Thread 0xb67bab70 00000000 did not get expected results! rc=0, errno=0

其实该小程序的意图很简单:创建了3个线程,每个线程内部会sleep 30秒,安装了一个信号INTERRUPT_SIGNAL,然后往第一个线程发送INTERRUPT_SIGNAL信号;可想而知,第一个线程会因为收到 信号而终止sleep,后面两个线程就只能等30秒了。

现在理解了NativeThread了,我们再看NativeDispatcher
首先我们得知道在ServerSocketChannelImpl中,nd被初始化为SocketDispatcher,见:

static {
    Util.load();
    initIDs();
    nd = new SocketDispatcher();
}

又因为linux下一切皆文件的思想(现实虽然不绝对),SocketDispatcher其实就是用FileDispatcher实现的,最终FileDispatcher也只是封装了一大堆native方法,一波三折,
关于FileDispatcher,这里先不详细讲解了,先针对nd.preClose(fd)和kill将implCloseSelectableChannel的过程说明白吧:
首先,我们要明白这样一个道理:在多线程环境下,总是很难知道什么时候可安全的关闭或释放资源(如fd),当一个线程A使用fd来读写,而另一个线程B关 闭或释放了fd,则A线程就会读写一个错误的文件或socket;为了防止这种情况出现,于是NIO就采用了经典的two-step处理方案:
第一步:创建一个socket pair,假设FDs为sp[2],先close掉sp[1],这样,该socket pair就成为了一个半关闭的链接;复制(dup2)sp[0]到fd(即为我们想关闭或释放的fd),这个时候,其他线程如果正在读写立即会获得EOF 或者Pipe Error,read或write方法里会检测这些状态做相应处理;
第二步:最后一个会使用到fd的线程负责释放
nd.preClose(fd)即为两步曲中的第一步,我们先来看其实现,最终定位到FileDispatcher.c,相关代码如下:

static int preCloseFD = -1; 
 
JNIEXPORT void JNICALL
Java_sun_nio_ch_FileDispatcher_init(JNIEnv *env, jclass cl)
{
    int sp[2];
    if (socketpair(PF_UNIX, SOCK_STREAM, 0, sp) < 0) {
    JNU_ThrowIOExceptionWithLastError(env, "socketpair failed");
        return;
    }
    preCloseFD = sp[0];
    close(sp[1]);
}
JNIEXPORT void JNICALL
Java_sun_nio_ch_FileDispatcher_preClose0(JNIEnv *env, jclass clazz, jobject fdo)
{
    jint fd = fdval(env, fdo);
    if (preCloseFD >= 0) {
    if (dup2(preCloseFD, fd) < 0)
        JNU_ThrowIOExceptionWithLastError(env, "dup2 failed");
    }
}

从上面两个函数实现,我们可以看到,在init函数中,创建了一个半关闭的socket pair,preCloseFD即为未关闭的一端,init在静态初始化时就会被执行;再来看关键的preClose0,它的确是采用dup2来复制 preCloseFD,这样一来,fd就被替换成了preCloseFD,这正是socket pair中未被关闭的一端。
既然nd.preClose(fd)只是预关闭,则真正执行关闭的逻辑肯定在这个kill中了,从代码逻辑上还是比较好懂的,if (!isRegistered())即表示该通道没有被注册,表示所有Selector都没有意愿关心这个通道了,则自然可以放心的关闭fd,通道与fd 的联系请看另一篇。
果断猜测kill中有nd.close(fd)这样的代码,不信请看:

public void kill() throws IOException {
synchronized (stateLock) {
    if (state == ST_KILLED)
    return;
    if (state == ST_UNINITIALIZED) {
            state = ST_KILLED;
    return;
        }
    assert !isOpen() && !isRegistered();
    nd.close(fd);
    state = ST_KILLED;
}
}

果然如此,这样一来,关闭二步曲就能够较安全的释放我们的fd资源了,至于nd.close(fd)的本地实现,这里就不讲了,肯定是采用了close(fd)的系统调用。
总的来说,通道的close就是为了断开它与内核fd的那点联系。

3、 begin() & end()

begin()和end()总是配对使用的,Channel和Selector均有自己的实现,所完成的功能也是有所区别的:
Selector的begin()和end()是这样使用的:

try {
    begin();
    pollWrapper.poll(timeout);
} finally {
    end();
}

我们先试想这样一个场景,poll阻塞过程中,Selector所在线程被中断了,会发生什么事?具体发生什么事这里就不深究了,至少,我们要通知一下辛苦poll的内核吧,不管是发信号也好,还是其他方式。
Selector不是有个天然的wakeup吗?似乎还挺优雅,为何不直接使用呢?是的,它们的确使用了,请看AbstractSelector中的实现:

protected final void begin() {
if (interruptor == null) {
    interruptor = new Interruptible() {
        public void interrupt() {
        AbstractSelector.this.wakeup();
        }};
}
AbstractInterruptibleChannel.blockedOn(interruptor);
if (Thread.currentThread().isInterrupted())
    interruptor.interrupt();
}
protected final void end() {
AbstractInterruptibleChannel.blockedOn(null);
}

我们看到,begin中出现了wakeup(),不过要理解begin和end,似乎我们先得弄明白AbstractInterruptibleChannel.blockedOn究竟在干什么:
AbstractInterruptibleChannel是这样写的:

static void blockedOn(Interruptible intr) {
sun.misc.SharedSecrets.getJavaLangAccess()
.blockedOn(Thread.currentThread(),intr);
    }

其中JavaLangAccess接口在java.lang.System中被实例化,它是这样写的:

private static void setJavaLangAccess() {
    // Allow privileged classes outside of java.lang
    sun.misc.SharedSecrets.setJavaLangAccess(new sun.misc.JavaLangAccess(){
        public sun.reflect.ConstantPool getConstantPool(Class klass) {
            return klass.getConstantPool();
        }
        public void setAnnotationType(Class klass, AnnotationType type) {
            klass.setAnnotationType(type);
        }
        public AnnotationType getAnnotationType(Class klass) {
            return klass.getAnnotationType();
        }
        public <E extends Enum<E>>
        E[] getEnumConstantsShared(Class<E> klass) {
            return klass.getEnumConstantsShared();
        }
        public void blockedOn(Thread t, Interruptible b) {
            t.blockedOn(b);
        }
        public void registerShutdownHook(int slot, Runnable r) {
            Shutdown.add(slot, r);
        }
    });
}

现在我们发现,JavaLangAccess的blockedOn实现,居然只有这么一句t.blockedOn(b),那么AbstractInterruptibleChannel.blockedOn实现就可以翻译成这样:
Thread.currentThread().blockedOn(intr),只因为该方法是包级私有的,并且Interruptible也是对我们不可见的,我们无法直接调用。
最后只用看java.lang.Thread中blockedOn的实现了:

private volatile Interruptible blocker;
void blockedOn(Interruptible b) {
    synchronized (blockerLock) {
        blocker = b;
    }
}

原来Thread类中包含Interruptible的私有成员blocker,blockedOn只是给它赋值而已。
到这里,要理解blockedOn究竟要做什么,就剩下理解这个blocker究竟有什么用,其实找到我们最常用的方法interrupt():

public void interrupt() {
if (this != Thread.currentThread())
    checkAccess();
    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();       // Just to set the interrupt flag
            b.interrupt();
            return;
        }
   }
   interrupt0();
}

看到了吧,b.interrupt(),java线程执行interrupt时,如果blocker有被赋值,则会执行它的interrupt。
最终回归到begin()和end(),豁然开朗:
begin()中的Interruptible实现的interrupt中就调用了wakeup(),这样一来,当内核poll阻塞中,java线程执行interrupt(),就会触发wakeup(),从而使得内核优雅的终止阻塞;
至于end(),就更好理解了,poll()结束后,就没有必要再wakeup了,所以就blockOn(null)了。
blockOn我们可以理解为,如果线程被中断,就附带执行我的这个interrupt方法吧。

以上讲解了Selector对begin()、end()的运用,下面就来看Channel是如何运用它们,实现在AbstractInterruptibleChannel(blockOn的提供者)中:

    protected final void begin() {
        if (interruptor == null) {
            interruptor = new Interruptible() {
                public void interrupt() {
                  synchronized (closeLock) {
                    if (!open)
                    return;
                    interrupted = true;
                    open = false;
                    try {
                       AbstractInterruptibleChannel.this.implCloseChannel();
                    } catch (IOException x) { }
                 }
            }};
        }
        blockedOn(interruptor);
        if (Thread.currentThread().isInterrupted())
            interruptor.interrupt();
}
 
protected final void end(boolean completed)
    throws AsynchronousCloseException
    {
        blockedOn(null);
        if (completed) {
            interrupted = false;
            return;
        }
        if (interrupted) throw new ClosedByInterruptException();
        if (!open) throw new AsynchronousCloseException();
    }

理解了Selector的begin()、end()实现,再来看这个,基本没什么难度,其实也可以猜想到,Selector既然在begin()和end()作用域内挂上wakeup,则Channel肯定会在begin()和end()作用域内挂上close之类的。
的确如此,它在begin()中挂上的是implCloseChannel()来实现关闭Channel。
Channel的使用地方非常多,在涉及到与内核交互(体现在那些native方法上)时,都会在头尾加上这个begin()、end()。
另外,似乎Channel的end有所不同,它还包含一个参数completed,用于表示begin()和end()之间的操作是否完成,意图也很明 显,begin()的interrupt()中已经设置,如果线程中断时,interrupted会被更改为true,这样在end()被执行的时候,如 果未完成,则会跑出ClosedByInterruptException异常,当然,如果操作确实没有被打断,则会将其平反。
见ServerSocketChannel#accept实现的代码端:

try {
begin();
if (!isOpen())
    return null;
thread = NativeThread.current();
for (;;) {
    n = accept0(this.fd, newfd, isaa);
    if ((n == IOStatus.INTERRUPTED) && isOpen())
    continue;
    break;
}
} finally {
thread = 0;
end(n > 0);
assert IOStatus.check(n);
}

n为accept0 native方法的返回值,当且仅当n>0时属于正常返回,所以才有了这个end(n > 0),从上述代码我们可以看到,当前这个begin()和end()就是防止在accept0时被中断所做的措施。

PS.本文来自:http://www.goldendoc.org/2011/08/java-nio-small-things/
欢迎大家关注:黄金档以及她的java nio系列

 

 

举报
zavakid
发帖于6年前 1回/2K+阅
顶部