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 开发 APP 接口--错误日志接口

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

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

安装时的问题: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解决:找到install/includes/lib_installer.php中的…

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

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

php决策管理,报表管理与数据分析:为系统未来发展规划提供决策依据,有效避免IT管理与投资的盲目??...

据了解,很多中大型企事业单位的IT基础结构具有复杂、分散等特征,并且信息化程度越高,数据类型越繁杂,数据量也越庞大。许多单位不得不付出极大的人力、物力对网络进行管理。而一个单位的信息化的程度和IT部门的服务水平&#xff0…

设置linux拨号服务端,CentOS Linux上搭建PPPoE服务器及拨号设置

CentOS下PPPoE拨号设置1.查看并安装拨号软件:[rootRedHat ~]# rpm -qa|grep pppoe[rootredhat ~]# yum -y install rp-pppoerp-pppoe.i686 0:3.10-8.el62.查看adsl-setup命令所在位置:[rootredhat ~]#whereis adsl-setupadsl-setup:或者直接搜索pppoe信息…

linux上pyenv卸载,在Ubuntu 18.04系统下安装pyenv的方法

本文介绍在Ubuntu 18.04操作系统下安装pyenv的方法,使用它可以进行Python多版本管理,目的是防止不同的Python版本因为不兼容而出现错误。安装pyenv其实非常的简单,只需要在系统终端中运行一条命令即可,以下是操作方法,…

linux防火墙作用是什么,Linux防火墙操作1

什么是防火墙防火墙可通过监测、限制、更改跨越防火墙的数据流,尽可能地对外部屏蔽网络内部的信息、结构和运行状况,以此来实现网络的安全保护。简单来讲 就是防止外界通过网络攻击Linux服务器的一个软件本次学习目标:防火墙的启停&#xff0…

lede 自定义linux,OpenWrt 和 LEDE 宣布正式合并

OpenWrt 和 LEDE 项目已在官网正式宣布合并,合并后的项目仍用 OpenWRT 命名。合并后的 OpenWrt 项目将按照 LEDE 制定的规范进行管理。原有的 LEDE 和 OpenWrt 项目的活跃成员将继续在合并后的 OpenWrt 上工作。LEDE 项目是 OpenWrt 的一个衍生项目,曾被…

旧衣回收小程序搭建有什么优势?

今年以来,旧衣回收行业分外火热,不断有创业者进入到市场中,其中不乏有年轻人,足以可见行业的火爆。 我国是人口大国,每个人闲置的衣物加在一起的数量难以计算,旧衣回收行业具有巨大的发展空间。 此外&…

c语言程序兔子反之问题,C语言解决兔子产子问题代码及解析

有一对兔子,从出生后的第3个月起每个月都生一对兔子。小兔子长到第3个月后每个月又生一对兔子,假设所有的兔子都不死,问30个月内每个月的兔子总数为多少?问题分析兔子数的规律,如下表所示:月数小兔子对数中…

c语言fork()创建线程,操作系统的创建原语是fork()还是creat()?

满意答案MythSwift2013.09.10采纳率:55% 等级:12已帮助:9461人一个进程就相当于一个主线程。fork一个进程与create一个线程的区别:fork进程:子进程复制父进程的进程环境。父进程结束不会影响子进程的运行。进程切换复制进程环境。create线…

android 最新 support,android support v7 下载-android support.v7包 官方最新版 - 河东下载站...

android support v7是一款功能非常实用的android在进行开发的过程中,必须要进行使用的一个包;这款软件作为一个功能非常全面的功能包,也是目前为止最新的功能包,是您这使用Android开发的时候,非常重要的一环&#xff1…

android aar jar制作,AndroidStudio aar、jar生成及其引用

aar生成新建项目,并在项目中新建Library module:banner,如图:在新建Module中编写完代码后,build整个工程后就会自动生成aar包,包的路径在新建Module 》 build 》outputs >aar目录下;aar引用在…

android 点击侧滑代码,代码分析Android实现侧滑菜单

Android 侧滑菜单的实现,参考网上的代码,实现侧滑菜单。最重要的是这个动画类UgcAnimations,如何使用动画类来侧滑的封装FlipperLayout。1、实现效果2、动画类UgcAnimationspackage com.mmsx.base;import android.content.Context;import and…

html游戏怎么编辑器,HTML的编辑器使用

使用 Notepad 或 TextEdit 来编写 HTML可以使用专业的 HTML 编辑器来编辑 HTML:Adobe DreamweaverMicrosoft Expression WebCoffeeCup HTML Editor(推荐学习:HTML入门教程)不过,我们同时推荐使用文本编辑器来学习 HTML,比如 Notep…

鸿蒙系统hifi,Apple Music将迎来重大更新 HiFi无损音质即将上线?

原标题:Apple Music将迎来重大更新 HiFi无损音质即将上线?前不久有媒体报道称,苹果将在当地时间5月18日推出HiFi版AppleMusic服务,新服务将为Apple Music用户提供无损的流媒体音乐,满足HiFi用户对高音质的追求。而在近…

华为笔记本会不会用鸿蒙,华为MateBook Pro笔记本为什么不用鸿蒙操作系统HarmonyO?...

头条上整天就是各种华为啊鸿蒙啊的,搞得好像人人都见过鸿蒙一样。还是大家以为操作系统就是做个APP或者王者荣耀?据我所知,鸿蒙OS现在我们能看到的,也就是几张PPT截图吧?整天动不动,这个为啥不用鸿蒙&#…

html 复选框name值,HTML(5)表单元素以及对各个表单元素的name、value属性的理解

我在学习表单元素的时候感觉很混乱,特别是 name value这两个属性,没有真正理解它们是干什么的,所以需要梳理一下。HTML表单元素主要有 HTML5新增的表单元素有三个下面梳理一下这些表单元素的用法并指出各元素的name、value属性含义1. 元素元…

苹果6怎样打开html,苹果iPhone的Safari浏览器使用技巧图解

  大多数的 iPhone 用户们都是用的 iOS 系统自带的 Safari 浏览器,不过很多用户们对 Safari 浏览器却不是很熟悉,因为 Safari 毕竟没有国产浏览器这么动我们!这厢脚本之家小编给大家介绍一些 iOS Safari 浏览器的一些你所不知道…