NIO 之 Channel

可参考之前写过的文章: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。


本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏微服务生态

淘宝Tedis组件究竟是个啥(一)

淘宝的Tedis组件究竟是个啥呢?可能有一些朋友没有听过这个名字,有一些朋友会经常使用,那么今天我就来和大家深入分析一下,它的使用和原理。

1052
来自专栏冰霜之地

如何设计并实现一个线程安全的 Map ?(下篇)

在上篇中,我们已经讨论过如何去实现一个 Map 了,并且也讨论了诸多优化点。在下篇中,我们将继续讨论如何实现一个线程安全的 Map。说到线程安全,需要从概念开始...

4347
来自专栏腾讯云Elasticsearch Service

Elasitcsearch 底层系列 Lucene 内核解析之 Stored Fields

Lucene 的 stored fields 主要用于行存文档需要保存的字段内容,每个文档的所有 stored fields 保存在一起,在查询请求需要返回字段...

1531
来自专栏Elson's web

从webpack4打包文件说起

一堆的webpack配置教程看腻了?这里有webpack4的打包及加载机制,要不了解一下?而这一切就得从打包文件说起。

3266
来自专栏你不就像风一样

[转载]一篇相当全面的Java NIO教程

NIO类包含在一个叫作java.nio包的包中。要了解NIO子系统不会取代java.io包中可用的基于流的I/O类,如果有对java.io基于流的I/O的如何工...

2872
来自专栏码云1024

MFC多线程

4106
来自专栏逆向技术

16位汇编语言第二讲系统调用原理,以及各个寄存器详解

   16位汇编语言第二讲系统调用原理,以及各个寄存器详解 昨天已将简单的写了一下汇编代码,并且执行了第一个显示到屏幕的helloworld 问题?   hel...

2200
来自专栏Java编程技术

基于Java注解和模块化生成树形业务文档的实践

一个新人快速掌握一个新系统业务逻辑的最好的工具是什么,是看代码?是debug?是看uc?是看demo?答案应该都不是,因为看代码和debug一来太耗时,二来系统...

1121
来自专栏用户2442861的专栏

web.xml配置详解

1、web.xml学名叫部署描述符文件,是在Servlet规范中定义的,是web应用的配置文件。

3101
来自专栏分布式系统进阶

Kafka消息的磁盘存储Kafka源码分析-汇总

可以看到使用FileMessageSet来操作Log文件, 使用OffsetIndex来操作Index文件

2042

扫码关注云+社区

领取腾讯云代金券