java nio channel抛出ClosedByInterruptException的情况

spark广播变量遇到这个异常,初始化数据库操作没加lazy

java nio里的channel是实现自InterruptibleChannel接口的,这个接口的注释里有说明,当正在操作这个channel的线程被其他线程中断,则会close这个channel,当前(被中断的)线程抛出一个ClosedByInterruptException异常。

我们今天在排查一个问题时,用户线程执行了下面的调用过程(从上往下):

org.apache.catalina.connector.CoyoteOutputStream.flush ---》
org.apache.tomcat.util.net.NioChannel.write ---》
sun.nio.ch.SocketChannelImpl.write ---》
java.nio.channels.spi.AbstractInterruptibleChannel.end  // 这里抛出异常

来看一下这个sun.nio.ch.SocketChannelImpl.write 方法内部,它的详细代码可以看这里 这里简化一些:

 public int  write(ByteBuffer buf) throws IOException {
  ...
  synchronized (writeLock) {
      ...
      try {
          begin();
          ...
      } finally {
          ...
          end(n > 0 || (n == IOStatus.UNAVAILABLE));
          ...
      }
  }
}

主要看一下它里面的beginend,先看end方法,异常抛出的地方:

protected final void end(boolean completed)
    throws AsynchronousCloseException
{
    blockedOn(null);
    Thread interrupted = this.interrupted;
    if (interrupted != null && interrupted == Thread.currentThread()) {
        interrupted = null;
        throw new ClosedByInterruptException();  
    }
    if (!completed && !open)
        throw new AsynchronousCloseException();
}

可以看到ClosedByInterruptException异常抛出的前提是当前线程被标记为已中断的;而这个判断是在begin方法里做的:

protected final void begin() {
    if (interruptor == null) {
        interruptor = new Interruptible() {
                public void interrupt(Thread target) {
                    synchronized (closeLock) {
                        if (!open)
                            return;
                        open = false;
                        interrupted = target;
                        try {
                            AbstractInterruptibleChannel.this.implCloseChannel();
                        } catch (IOException x) { }
                    }
                }};
    }
    blockedOn(interruptor);
    Thread me = Thread.currentThread();
    if (me.isInterrupted()) // 检测当前线程是否已中断
        interruptor.interrupt(me);
}

begin方法里,检查当前线程如果是中断状态,用引用记录起来(为了后边比较使用),并关闭了channel。

现在用scala模拟一下这个异常:

$ cat server

import java.nio._
import java.net._
import java.nio.channels._

val serverSock = ServerSocketChannel.open()
serverSock.socket().bind(new InetSocketAddress(54321))

val  ch:SocketChannel = serverSock.accept()

println("ok,received")

Thread.currentThread().interrupt() //中断当前线程
try{
    ch.socket().getOutputStream().write(200)
}catch{
    case e:Throwable => println(e)
}

上面的这段脚本,用nio模拟了一个server等待客户端的链接,当链接过来的时候,中断当前线程,然后继续channel进行处理的时候会捕获到ClosedByInterruptException异常。

启动上面的脚本

$ scala server

在另一个终端下模拟一次client请求:

$ telnet localhost 54321

这时会看到server端的输出信息:

$ scala server
ok,received
java.nio.channels.ClosedByInterruptException

你可能感兴趣的:(java nio channel抛出ClosedByInterruptException的情况)