NIO 之 Channel

作者: jijs | 来源:发表于2018-05-05 06:05 被阅读458次

    可参考之前写过的文章:NIO 之 Channel实现原理

    概述

    通道( Channel)是 java.nio 的主要创新点。它们既不是一个扩展也不是一项增强,而是全新、极好的 Java I/O 示例,提供与 I/O 服务的直接连接。 Channel 用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。

    Channel 接口定义

    public interface Channel extends Closeable {
        public boolean isOpen();
        public void close() throws IOException;
    }
    

    Channel 接口,只抽象了 isOpen() 方法和 close() 方法。

    是否感觉很奇怪,为什么没有 open() 方法?

    Channel 概述

    I/O 分为File I/O 和 Stream I/O。
    File I/O 对应的是文件(file)通道。
    Stream I/O 对应的是( socket)通道。

    FileChannel 类和三个 socket 通道类: SocketChannel、 ServerSocketChannel 和 DatagramChannel。

    通道可以以多种方式创建。 Socket 通道有可以直接创建新 socket 通道的工厂方法。但是一个FileChannel 对象却只能通过在一个打开的 RandomAccessFile、 FileInputStream 或 FileOutputStream对象上调用 getChannel( )方法来获取。您不能直接创建一个 FileChannel 对象。(这也是 Channel 接口没有定义 open() 方法的原因)。

    ByteChannel

    通过源码发现每一个 file 或 socket 通道都实现ByteChannel。

    ByteChannel

    public interface ByteChannel extends ReadableByteChannel, WritableByteChannel { }
    

    ReadableByteChannel

    public interface ReadableByteChannel extends Channel {
        public int read(ByteBuffer dst) throws IOException;
    }
    

    WritableByteChannel

    public interface WritableByteChannel extends Channel{
        public int write(ByteBuffer src) throws IOException;
    }
    

    通道可以是单向或者双向的。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据。

    每一个 file 或 socket 通道都实现全部三个接口。从类定义的角度而言,这意味着全部 file 和 socket 通道对象都是双向的。这对于 sockets 不是问题,因为它们一直都是双向的,不过对于 files 却是个问题了。

    一个文件可以在不同的时候以不同的权限打开。从 FileInputStream 对象的getChannel( )方法获取的 FileChannel 对象是只读的,不过从接口声明的角度来看却是双向的,因为
    FileChannel 实现 ByteChannel 接口。在这样一个通道上调用 write( )方法将抛出未经检查的NonWritableChannelException 异常,因为 FileInputStream 对象总是以 read-only 的权限打开文件。一个连接到只读文件的 Channel 实例不能进行写操作,即使该实例所属的类可能有 write( )方法。基于此,程序员需要知道通道是如何打开的,避免试图尝试一个底层 I/O服务不允许的操作。

    阻塞非阻塞

    通道可以以阻塞( blocking)或非阻塞( nonblocking)模式运行。非阻塞模式的通道永远不会让调用的线程休眠。请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流的( stream-oriented)的通道,如 sockets 和 pipes 才能使用非阻塞模式。file 通道是不能以非阻塞的模式运行。

    Channel.close()

    与缓冲区(Buffer)不同,通道(Channel)不能被重复使用。一个打开的通道即代表与一个特定 I/O 服务的特定连接并封装该连接的状态。当通道关闭时,那个连接会丢失,然后通道将不再连接任何东西。

    调用通道的close( )方法时,可能会导致在通道关闭底层I/O服务的过程中线程暂时阻塞,哪怕该通道处于非阻塞模式。通道关闭时的阻塞行为(如果有的话)是高度取决于操作系统或者文件系统的。

    源码简略如下:

        //该代码是 FileChannel 的关闭方法 (在FileChannel 的父类 AbstractInterruptibleChannel 中)
        public final void close() throws IOException {
            synchronized (closeLock) {
                if (!open)
                    return;
                open = false;
                implCloseChannel();
            }
        }
    

    从上面代码中可以分析出在一个通道上多次调用close( )方法是没有坏处的,但是如果第一个线程在close( )方法中阻塞(使用synchronized 锁),那么在它完成关闭通道之前,任何其他调用close( )方法都会阻塞。后续在该已关闭的通道上调用close( )不会产生任何操作,只会立即返回。

    Channel.isOpen( )

    可以通过 isOpen( )方法来测试通道的开放状态。如果返回 true 值,那么该通道可以使用。如果返回 false 值,那么该通道已关闭,不能再被使用。尝试进行任何需要通道处于开放状态作为前提的操作,如读、写等都会导致 ClosedChannelException 异常。

    源码简略如下:

    //该代码在FileChannel 的子类中实现的
    public class FileChannelImpl  extends FileChannel{
    
        public int read(ByteBuffer dst) throws IOException {
            ensureOpen();
            ......
        }
    
        public int write(ByteBuffer src) throws IOException {
            ensureOpen();
            ......
        }
        private void ensureOpen() throws IOException {
            if (!isOpen())
                throw new ClosedChannelException();
        }
    }
    

    通道响应 Interrupt 中断

    通道引入了一些与关闭和中断有关的新行为。通道实现 InterruptibleChannel 接口。
    如果一个线程在一个通道上被阻塞并且同时被中断(由调用该被阻塞线程的 interrupt( )方法的另一个线程中断),那么该通道将被关闭,该被阻塞线程也会产生一个 ClosedByInterruptException 异常。

    源码简略如下:

    public class FileChannelImpl extends FileChannel{
    
        public int write(ByteBuffer src) throws IOException {
           ......
           end(n > 0);
           ......
        }
    
        public int read(ByteBuffer dst) throws IOException {
           ......
           end(n > 0);
           ......
        }
    
        public FileLock lock(long position, long size, boolean shared)  throws IOException {
           ......
           end(n > 0);
           ......
        }
        .......
    }
    
    
    public abstract class AbstractInterruptibleChannel
        implements Channel, InterruptibleChannel {
    
        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();
        }
        ......
    }
    

    Interrupt 关闭 Channel 实例

    import java.io.File;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class ChannelInterruptDemo {
    
        public static void main(String[] args) throws Exception  {
            FileChannel fc = new RandomAccessFile(new File("d:/a.txt"), "rw").getChannel();
            System.out.println("Channel isOpen : " + fc.isOpen());
            
            Thread t = new Thread(new Task(fc));
            t.start();
            t.interrupt();
            t.join();
            
            System.out.println("Channel isOpen : " + fc.isOpen());
            
            fc.close();
        }
    }
    class Task implements Runnable{
        FileChannel fc;
        
        public Task(FileChannel fc) {
            this.fc = fc;
        }
    
        @Override
        public void run() {
            try {
                fc.position(Integer.MAX_VALUE/2);
                fc.write(ByteBuffer.wrap("hello".getBytes()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    运行结果: 输出结果

    从结果中发现,只要Channel 所在的线程 interrupt 就会自动关闭channel。


    喜欢本文的朋友们,欢迎长按下图关注订阅号 java404,收听更多精彩的内容

    java404

    相关文章

      网友评论

      本文标题:NIO 之 Channel

      本文链接:https://www.haomeiwen.com/subject/gvmhrftx.html