java 成神之路程序员java 网络编程

NIO 之 Channel

2018-05-05  本文已影响458人  jijs

可参考之前写过的文章: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
上一篇下一篇

猜你喜欢

热点阅读