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。
喜欢本文的朋友们,欢迎长按下图关注订阅号 java404,收听更多精彩的内容
java404