来源:木杉的博客 ,

imushan.com/2018/08/01/java/language/JDK源码阅读-InterruptibleChannel与可中断IO/

Java传统IO是不支持中断的,所以如果代码在read/write等操作阻塞的话,是无法被中断的。这就无法和Thead的interrupt模型配合使用了。JavaNIO众多的升级点中就包含了IO操作对中断的支持。InterruptiableChannel表示支持中断的Channel。我们常用的FileChannel,SocketChannel,DatagramChannel都实现了这个接口。

InterruptibleChannel接口

public interface InterruptibleChannel extends Channel{

/**

* 关闭当前Channel

*

* 任何当前阻塞在当前channel执行的IO操作上的线程,都会收到一个AsynchronousCloseException异常

*/

public void close() throws IOException;

}

InterruptibleChannel接口没有定义任何方法,其中的close方法是父接口就有的,这里只是添加了额外的注释。

AbstractInterruptibleChannel实现了InterruptibleChannel接口,并提供了实现可中断IO机制的重要的方法,比如begin(),end()。

在解读这些方法的代码前,先了解一下NIO中,支持中断的Channel代码是如何编写的。

第一个要求是要正确使用begin()和end()方法:

boolean completed = false;

try {

begin();

completed = ...;    // 执行阻塞IO操作

return ...;         // 返回结果

} finally {

end(completed);

}

NIO规定了,在阻塞IO的语句前后,需要调用begin()和end()方法,为了保证end()方法一定被调用,要求放在finally语句块中。

第二个要求是Channel需要实现java.nio.channels.spi.AbstractInterruptibleChannel#implCloseChannel这个方法。AbstractInterruptibleChannel在处理中断时,会调用这个方法,使用Channel的具体实现来关闭Channel。

接下来我们具体看一下begin()和end()方法是如何实现的。

begin方法

// 保存中断处理对象实例

private Interruptible interruptor;

// 保存被中断线程实例

private volatile Thread interrupted;

protected final void begin(){

// 初始化中断处理对象,中断处理对象提供了中断处理回调

// 中断处理回调登记被中断的线程,然后调用implCloseChannel方法,关闭Channel

if (interruptor == null) {

interruptor = new Interruptible() {

public void interrupt(Thread target){

synchronized (closeLock) {

// 如果当前Channel已经关闭,则直接返回

if (!open)

return;

// 设置标志位,同时登记被中断的线程

open = false;

interrupted = target;

try {

// 调用具体的Channel实现关闭Channel

AbstractInterruptibleChannel.this.implCloseChannel();

} catch (IOException x) { }

}

}};

}

// 登记中断处理对象到当前线程

blockedOn(interruptor);

// 判断当前线程是否已经被中断,如果已经被中断,可能登记的中断处理对象没有被执行,这里手动执行一下

Thread me = Thread.currentThread();

if (me.isInterrupted())

interruptor.interrupt(me);

}

从begin()方法中,我们可以看出NIO实现可中断IO操作的思路,是在Thread的中断逻辑中,挂载自定义的中断处理对象,这样Thread对象在被中断时,会执行中断处理对象中的回调,这个回调中,执行关闭Channel的操作。这样就实现了Channel对线程中断的响应了。

接下来重点就是研究“Thread添加中断处理逻辑”这个机制是如何实现的了,是通过blockedOn方法实现的:

static void blockedOn(Interruptible intr){         // package-private

sun.misc.SharedSecrets.getJavaLangAccess().blockedOn(Thread.currentThread(),intr);

}

blockedOn方法使用的是JavaLangAccess的blockedOn方法。

SharedSecrets是一个神奇而糟糕的类,为啥说是糟糕呢,因为这个方法的存在,就是为了访问JDK类库中一些因为类作用域限制而外部无法访问的类或者方法。JDK很多类与方法是私有或者包级别私有的,外部是无法访问的,但是JDK在本身实现的时候又存在互相依赖的情况,所以为了外部可以不依赖反射访问这些类或者方法,在sun包下,存在这么一个类,提供了各种超越限制的方法。

SharedSecrets.getJavaLangAccess()方法返回JavaLangAccess对象。JavaLangAccess对象就和名称所说的一样,提供了java.lang包下一些非公开的方法的访问。这个类在System初始化时被构造:

// java.lang.System#setJavaLangAccess

private static void setJavaLangAccess(){

sun.misc.SharedSecrets.setJavaLangAccess(new sun.misc.JavaLangAccess(){

public void blockedOn(Thread t, Interruptible b){

t.blockedOn(b);

}

//...

});

}

可以看出,sun.misc.JavaLangAccess#blockedOn保证的就是java.lang.Thread#blockedOn这个包级别私有的方法:

/* The object in which this thread is blocked in an interruptible I/O

* operation, if any.  The blocker's interrupt method should be invoked

* after setting this thread's interrupt status.

*/

private volatile Interruptible blocker;

private final Object blockerLock = new Object();

/* Set the blocker field; invoked via sun.misc.SharedSecrets from java.nio code

*/

void blockedOn(Interruptible b){

// 串行化blocker相关操作

synchronized (blockerLock) {

blocker = b;

}

}

而这个方法也非常简单,就是设置java.lang.Thread#blocker变量为之前提到的中断处理对象。而且从注释中可以看出,这个方法就是专门为NIO设计的,注释都非常直白的提到了,NIO的代码会通过sun.misc.SharedSecrets调用到这个方法。。

接下来就是重头戏了,看一下Thread在中断时,如何调用NIO注册的中断处理器:

public void interrupt(){

if (this != Thread.currentThread())

checkAccess();

synchronized (blockerLock) {

Interruptible b = blocker;

// 如果NIO设置了中断处理器,则只需Thread本身的中断逻辑后,调用中断处理器的回调函数

if (b != null) {

interrupt0();           // 这一步会设置interrupt标志位

b.interrupt(this);

return;

}

}

// 如果没有的话,就走普通流程

interrupt0();

}

end方法

begin()方法负责添加Channel的中断处理器到当前线程。end()是在IO操作执行完/中断完后的操作,负责判断中断是否发生,如果发生判断是当前线程发生还是别的线程中断把当前操作的Channel给关闭了,对于不同的情况,抛出不同的异常。

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();

}

// 如果这次没有读取到数据,并且Channel被另外一个线程关闭了,则排除Channel被异步关闭的异常

// 但是如果这次读取到了数据,就不能抛出异常,因为这次读取的数据是有效的,需要返回给用户的(重要逻辑)

if (!completed && !open)

throw new AsynchronousCloseException();

}

通过代码可以看出,如果是当前线程被中断,则抛出ClosedByInterruptException异常,表示Channel因为线程中断而被关闭了,IO操作也随之中断了。

如果是当前线程发现Channel被关闭了,并且是读取还未执行完毕的情况,则抛出AsynchronousCloseException异常,表示Channel被异步关闭了。

end()逻辑的活动图如下:

场景分析

并发的场景分析起来就是复杂,上面的代码不多,但是场景很多,我们以sun.nio.ch.FileChannelImpl#read(java.nio.ByteBuffer)为例分析一下可能的场景:

A线程read,B线程中断A线程:A线程抛出ClosedByInterruptException异常

A,B线程read,C线程中断A线程

A被中断时,B刚刚进入read方法:A线程抛出ClosedByInterruptException异常,B线程ensureOpen方法抛出ClosedChannelException异常

A被中断时,B阻塞在底层read方法中:A线程抛出ClosedByInterruptException异常,B线程底层方法抛出异常返回,end方法中抛出AsynchronousCloseException异常

A被中断时,B已经读取到数据:A线程抛出ClosedByInterruptException异常,B线程正常返回

sun.nio.ch.FileChannelImpl#read(java.nio.ByteBuffer)代码如下:

public int read(ByteBuffer dst) throws IOException{

ensureOpen();  // 1

if (!readable) // 2

throw new NonReadableChannelException();

synchronized (positionLock) {

int n = 0;

int ti = -1;

try {

begin();

ti = threads.add();

if (!isOpen())

return 0; // 3

do {

n = IOUtil.read(fd, dst, -1, nd); // 4

} while ((n == IOStatus.INTERRUPTED) && isOpen());

return IOStatus.normalize(n);

} finally {

threads.remove(ti);

end(n > 0);

assert IOStatus.check(n);

}

}

}

总结

在JavaIO时期,人们为了中断IO操作想了不少方法,核心操作就是关闭流,促使IO操作抛出异常,达到中断IO的效果。NIO中,将这个操作植入了java.lang.Thread#interrupt方法,免去用户自己编码特定代码的麻烦。使IO操作可以像其他可中断方法一样,在中断时抛出ClosedByInterruptException异常,业务程序捕获该异常即可对IO中断做出响应。

参考资料

java – What does JavaLangAccess.blockedOn(Thread t, Interruptible b) do? – Stack Overflow

https://stackoverflow.com/questions/8544891/what-does-javalangaccess-blockedonthread-t-interruptible-b-do

Java NIO 那些躲在角落的细节

https://www.oschina.net/question/138146_26027

java io中断_JDK源码阅读:InterruptibleChannel 与可中断 IO相关推荐

  1. java cloneable 接口_JDK源码阅读笔记-Cloneable接口

    JDK 版本:1.8 代码地址 1.前言 clone方法能方便的获得一个对象的拷贝,但其中也有些细节需要注意. 2.实现注意事项 2.1 要调用 clone 方法必须实现 Cloneable 接口 如 ...

  2. java collection源码_jdk源码阅读Collection实例分析

    jdk源码阅读Collection详解 见过一句夸张的话,叫做"没有阅读过jdk源码的人不算学过java".从今天起开始精读源码.而适合精读的源码无非就是java.io,.util ...

  3. java jdk 类加载机制_JDK源码阅读之类加载

    java类加载 类的生命周期(类加载过程) LLIUU+VPR 加载(Loading) 链接(Linking) 验证(Verification) 准备(Preparation) 解析(Resoluti ...

  4. Java多线程类FutureTask源码阅读以及浅析

    FutureTask是一个具体的实现类,实现了RunnableFuture接口,RunnableFuture分别继承了Runnable和Future接口,因此FutureTask类既可以被线程执行,又 ...

  5. go 中 select 源码阅读

    Python微信订餐小程序课程视频 https://blog.csdn.net/m0_56069948/article/details/122285951 Python实战量化交易理财系统 https ...

  6. MSI_MSI-X中断之源码分析

    MSI_MSI-X中断之源码分析 文章目录 MSI_MSI-X中断之源码分析 一. 怎么发出MSI/MSI-X中断 1.1 在RK3399上体验 1.1.1 安装工具 1.1.2 查看设备MSI-X信 ...

  7. 走过的路-java源码阅读之路

    源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 一.人生三种境界: 1.昨夜西风凋碧树,独上高楼望尽天涯路.           2.衣带渐宽终不悔,为伊消得人憔悴.           ...

  8. 源码阅读(34):Java中线程安全的Queue、Deque结构——ArrayBlockingQueue(4)

    (接上文<源码阅读(33):Java中线程安全的Queue.Deque结构--ArrayBlockingQueue(3)>) 2.3.3.3.forEachRemaining() 方法 f ...

  9. Java源码阅读的真实体会(一种学习思路)

    刚才在论坛不经意间,看到有关源码阅读的 帖子 .回想自己前几年,阅读源码那种兴奋和成就感( 1 ),不禁又有一种激动.  源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我 ...

最新文章

  1. C++/C++11中左值、左值引用、右值、右值引用的使用
  2. Linux生成ssh公钥免密码登录远程主机和Xshell跨跳板机登录
  3. qt 历史记录控件_[QT] 记录一些使用技巧
  4. leetcode1070. 产品销售分析 III(SQL)
  5. OpneCV之图像的平移、翻转、旋转、缩放、裁剪(笔记04)
  6. 前端模板技术的全面总结
  7. 解决远程主机不能cv问题
  8. Windows文件传输小工具,网络传输文件,内网传输
  9. Uva 816 Abbott's Revenge 紫书165页例题
  10. 如何获取视频文件的扩展名
  11. php实训报告摘要部分怎么写,毕业论文的摘要部分怎么写(附摘要范文)
  12. c语言字节溢出,C语言变量定义与数据溢出(初学者)
  13. 2019第五届中国诗歌春晚致敬先贤
  14. WinRAR命令行用法
  15. JS实现记住用户密码
  16. MySQL 生成累计乘积
  17. 运行JS脚本的几种方式
  18. deepFM model
  19. 华师计算机学院教师资格证,教师资格认证
  20. 8.Redis- 集群:AKF拆分(y轴和z轴),twemproxy,predixy,cluster

热门文章

  1. hilbert谱 matlab,怎么在matlab中做信号hilbert边际谱分析
  2. 国家开放大学计算机应用模块3客观题答案,国家开放大学《计算机应用基础》考试与答案形考任务模块3模块3Excel2010电子表格系统—客观题答案.pdf...
  3. Qt实现界面的窗口的局部动态添加并布局
  4. cad多线段长度计算总和_没想到啊,我平时用的CAD多段线有这么多学问
  5. pbl和sbl_谈PBL和SBL教学法结合模式
  6. vue 时区转换_vue---时间戳转换
  7. java txt 按行读取_java读取按行txt文件
  8. java 代理ip工具类_Java基础之java处理ip的工具类
  9. linux 关闭ext3日志,ssh – 可以在Linux(ext3)上减慢日志写入速度吗?
  10. linux subversion 根目录检出,经验总结:详解Linux下Subversion的安装配置记录 下