java io中断_JDK源码阅读:InterruptibleChannel 与可中断 IO

来源:木杉的博客 ,

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

}

68bfdf3476ef0c6acd06a67b23f67ce1.gif

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()逻辑的活动图如下:

68bfdf3476ef0c6acd06a67b23f67ce1.gif

场景分析

并发的场景分析起来就是复杂,上面的代码不多,但是场景很多,我们以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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/534457.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java值栈_Struts2学习笔记-Value Stack(值栈)和OGNL表达式

只是本人的Struts2学习笔记,关于Value Stack(值栈)和OGNL表达式,把我知道的都说出来,希望对大家有用。一,值栈的作用记录处理当前请求的action的数据。二,小例子有两个action:Action1和Action2Action1有两个…

php项目实战流程_一个完整的php流程管理实例代码分享

1. 添加新流程页面:请选择流程节点:session_start();include("../DBDA.class.php");$db new DBDA();$suser "select * from users";$auser $db->Query($suser);foreach($auser as $v){echo " {$v[2]} ";}?>$att…

php cdata,PHPcdata处理(详细介绍)_PHP教程

PHPcdata处理(详细介绍)_PHP教程当时在网上找了一个CDATA的转换器, 修改之后, 将CDATA标签给过滤掉。如下代码如下:// States://// out// // // // // // // // in// ]// ]]//// (Yes, the states a represented by strings.)//$state out;$a s…

PHP 与go 通讯,Golang和php通信

不同语言之间的通信方式有很多种,这里我介绍一种最简单通信方式,json-rpc。Golang自带json-rpc包,使用起来十分简单,示例如下,提供一个简单echo server。package mainimport ("fmt""net""net…

php 接口日志,PHP 开发 APP 接口--错误日志接口

APP 上线以后可能遇到的问题:① APP 强退② 数据加载失败③ APP 潜在问题错误日志需要记录的内容数据表 error_log 字段:idapp_id:app 类别 iddid:客户端设备号version_id:版本号version_mini:小版本号erro…

php 空模块,tp5.1配置空模块,空方法

config/app.php//默认的空模块名empty_module>index,controller/Error.php<?php namespace app\index\controller;use Env;use think\Controller;class Error extends Controller {//Db::connect(db_ck)//全局MISS路由 在route.php里面设置找不到控制器默认处理//Route:…

centos7php自启动,centos7系统下nginx安装并配置开机自启动操作

这篇文章主要介绍了centos7系统下nginx安装并配置开机自启动操作方法,非常不错&#xff0c;具有参考借鉴价值&#xff0c;需要的朋友可以参考下这篇文章主要介绍了centos7系统下nginx安装并配置开机自启动操作方法,非常不错&#xff0c;具有参考借鉴价值&#xff0c;需要的朋友…

时钟php,php+js液晶时钟

php代码$size_small5;//液晶宽度$size_big25;//液晶长度$distance10;//间距$color_back"#DDDDDD";$color_dark"#CCCCCC";$color_light"#000000";$number0;?>Timer|www.ibtf.net|www.bitefu.netfunction swapcolor(obj,onoff)//改变颜色{if (…

r和matlab学哪个,初学者求教‘r*’是什么意思啊

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼PLOT(X,Y,S) where S is a character string made from one elementfrom any or all the following 3 columns:b blue . point - solidg green o circle : dottedr red x x-mark -. dashdotc cyan plus -- dashedm magenta * star…

php swoole 心跳,聊聊swoole的心跳

来自&#xff1a;桶哥的一篇关于swoole的心跳的文章&#xff0c;作为Swoole顾问(顾得上就问,是为「顾问」)得推一下这篇文章&#xff0c;最后只留下一配置&#xff0c;其实我也不是太明白原理&#xff0c;我在想如果是局域网里还需要心跳&#xff1f;—————————————…

mysql 查询 投影,MySql-连接查询

连接查询Chloe 友好支持多表连接查询&#xff0c;一切都可以用 lambda 表达式操作&#xff0c;返回类型可以是自定义类型&#xff0c;也可以是匿名类型。强类型开发&#xff0c;编译可见错误&#xff0c;容错率高。1.建立连接&#xff1a;var user_city_province context.Quer…

php 递归栏目名叠加,thinkPHP实现递归循环栏目并按照树形结构无限极输出的方法,thinkphp递归...

thinkPHP实现递归循环栏目并按照树形结构无限极输出的方法&#xff0c;thinkphp递归本文实例讲述了thinkPHP实现递归循环栏目并按照树形结构无限极输出的方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;这里使用thinkphp递归循环栏目按照树形结构无限极输出&#…

php cannot call constructor,安装ECshop普遍问题的解决方法

安装时的问题&#xff1a;1.Strict Standards: Non-static method cls_image::gd_version() should not be called statically in /usr/local/httpd2/htdocs/upload/install/includes/lib_installer.php on line 31解决&#xff1a;找到install/includes/lib_installer.php中的…

wind试用版 matlab,免费产品试用 - MATLAB Simulink

请选择其一AlabamaAlaska美属萨摩亚APO/FPO AAAPO/FPO AEAPO/FPO APArizonaArkansasCaliforniaCaroline IslandsColoradoConnecticutDelawareDistrict of ColumbiaFlorida格鲁吉亚关岛HawaiiIdahoIllinoisIndianaIowaKansasKentuckyLouisianaMaineMariana Islands马绍尔群岛Mar…

php yii2 sns,GitHub - yggphpcoder/iisns: 基于 yii2 的 sns 社区系统,一站式解决社区建站...

iisns - 地球村入口iiSNS 是基于 yii2 的 SNS 社区系统&#xff0c;一站式解决社区建站。可以写文章&#xff0c;做记录&#xff0c;上传图片&#xff0c;论坛聊天等。还可以用来做内容管理系统(CMS)。iiSNS 是一个免费的开源项目&#xff0c;在 MIT 许可证下授权发布。特点与功…

php mvc 商城,基于MVC框架的小型网上商城设计

2&#xff0e;本人对课题任务书提出的任务要求及实现预期目标的可行性分析基于MVC框架的小型网上商城实现的功能&#xff1a;商品的浏览、查询、购买&#xff0c;会员注册以及会员订单的查询等&#xff0c;方便商场活动&#xff0c;该系统基本实现了网上商城的应有功能。该系统…

php 做更新进度条,PHP exec()后更新Bootstrap进度条

我使用PHP来运行一个python脚本&#xff0c;并且在脚本执行后需要更新一个进度条。进度条更新后&#xff0c;将执行另一个脚本&#xff0c;依此类推。这里是我的代码如此的票价。我试图用JavaScript来实现。它没有解决Button Textif (isset($_POST[turn])){exec("sudo pyt…

zblog php和asp功能,ZBlog是否适合PHP或ASP?我们该如何选择?

我最近玩了zblog一段时间&#xff0c;对于大多数第一次联系zblog的博客&#xff0c;他们会问zblog是否适合PHP或ASP&#xff1f;我们该如何选择&#xff1f;事实上&#xff0c;我真的不明白这个问题。我个人更喜欢PHP。今天我将整理出来并对PHP版本和ASP版本进行比较&#xff0…

php js记住密码功能,jquery.cookie.js实现用户登录保存密码功能的方法_jquery

本文实例讲述了jquery.cookie.js实现用户登录保存密码功能的方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;需要导入的js有jquery.js和jquery.cookie.js在页面加载时首先尝试获取cookie的值&#xff0c;如果cookie有值&#xff0c;则将获取到的值填入输入框中&am…

oracle dbf 超大,oracle 数据库users01.dbf文件过大 转移方法

如果出现 linux 拒绝错误&#xff0c;可以把目录权限 该为777由于在安装的时候将Oracle安装到了C盘&#xff0c;表空间也创建到了C盘(当时没有在意)&#xff0c;等项目进行到了中期&#xff0c;发现C盘的空间不够用了。此时&#xff0c;一个较好的解决办法就是将表空间的文件转…