相应式网站,wordpress主题配置,九年级上册信息技术做网站,中国站长网站来源#xff1a;木杉的博客 #xff0c;imushan.com/2018/08/01/java/language/JDK源码阅读-InterruptibleChannel与可中断IO/Java传统IO是不支持中断的#xff0c;所以如果代码在read/write等操作阻塞的话#xff0c;是无法被中断的。这就无法和Thead的interrupt模型配合使…来源木杉的博客 imushan.com/2018/08/01/java/language/JDK源码阅读-InterruptibleChannel与可中断IO/Java传统IO是不支持中断的所以如果代码在read/write等操作阻塞的话是无法被中断的。这就无法和Thead的interrupt模型配合使用了。JavaNIO众多的升级点中就包含了IO操作对中断的支持。InterruptiableChannel表示支持中断的Channel。我们常用的FileChannelSocketChannelDatagramChannel都实现了这个接口。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方法关闭Channelif (interruptor null) {interruptor new Interruptible() {public void interrupt(Thread target){synchronized (closeLock) {// 如果当前Channel已经关闭则直接返回if (!open)return;// 设置标志位同时登记被中断的线程open false;interrupted target;try {// 调用具体的Channel实现关闭ChannelAbstractInterruptibleChannel.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-privatesun.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#setJavaLangAccessprivate 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 blockers interrupt method should be invoked* after setting this threads 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线程readB线程中断A线程A线程抛出ClosedByInterruptException异常AB线程readC线程中断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(); // 1if (!readable) // 2throw new NonReadableChannelException();synchronized (positionLock) {int n 0;int ti -1;try {begin();ti threads.add();if (!isOpen())return 0; // 3do {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 Overflowhttps://stackoverflow.com/questions/8544891/what-does-javalangaccess-blockedonthread-t-interruptible-b-doJava NIO 那些躲在角落的细节https://www.oschina.net/question/138146_26027