给操作系统,也使得操作系统能够对我们的 IO 进行一定程度的优化。
在 Linux 中其实也是有异步 IO 系统实现的,但是限制比较多,性能也一般,所以 JDK 采用了自建线程池的方式。
本文还是以实用为主,想要了解更多信息请自行查找其他资料,下面对 Java 异步 IO 进行实践性的介绍。
总共有三个类需要我们关注,分别是 AsynchronousSocketChannel,AsynchronousServerSocketChannel 和 AsynchronousFileChannel,只不过是在之前介绍的 FileChannel、SocketChannel 和 ServerSocketChannel 的类名上加了个前缀 Asynchronous。
Java 异步 IO 提供了两种使用方式,分别是返回 Future 实例和使用回调函数。
1、返回 Future 实例
返回 java.util.concurrent.Future 实例的方式我们应该很熟悉,JDK 线程池就是这么使用的。Future 接口的几个方法语义在这里也是通用的,这里先做简单介绍。
- future.isDone();
判断操作是否已经完成,包括了正常完成、异常抛出、取消
- future.cancel(true);
取消操作,方式是中断。参数 true 说的是,即使这个任务正在执行,也会进行中断。
- future.isCancelled();
是否被取消,只有在任务正常结束之前被取消,这个方法才会返回 true
- future.get();
这是我们的老朋友,获取执行结果,阻塞。
- future.get(10, TimeUnit.SECONDS);
如果上面的 get() 方法的阻塞你不满意,那就设置个超时时间。
2、提供 CompletionHandler 回调函数
java.nio.channels.CompletionHandler 接口定义:
public interface CompletionHandler<V,A> {
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);
}
注意,参数上有个 attachment,虽然不常用,我们可以在各个支持的方法中传递这个参数值
AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(null);
// accept 方法的第一个参数可以传递 attachment
listener.accept(attachment, new CompletionHandler<AsynchronousSocketChannel, Object>() {
public void completed(
AsynchronousSocketChannel client, Object attachment) {
//
}
public void failed(Throwable exc, Object attachment) {
//
}
});
AsynchronousFileChannel
网上关于 Non-Blocking IO 的介绍文章很多,但是 Asynchronous IO 的文章相对就少得多了,所以我这边会多介绍一些相关内容。
首先,我们就来关注异步的文件 IO,前面我们说了,文件 IO 在所有的操作系统中都不支持非阻塞模式,但是我们可以对文件 IO 采用异步的方式来提高性能。
下面,我会介绍 AsynchronousFileChannel 里面的一些重要的接口,都很简单,读者要是觉得无趣,直接滑到下一个标题就可以了。
实例化:
AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("/Users/hongjie/test.txt"));
一旦实例化完成,我们就可以着手准备将数据读入到 Buffer 中:
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> result = channel.read(buffer, 0);
异步文件通道的读操作和写操作都需要提供一个文件的开始位置,文件开始位置为 0
除了使用返回 Future 实例的方式,也可以采用回调函数进行操作,接口如下:
public abstract <A> void read(ByteBuffer dst,
long position,
A attachment,
CompletionHandler<Integer,? super A> handler);
顺便也贴一下写操作的两个版本的接口:
public abstract Future<Integer> write(ByteBuffer src, long position);
public abstract <A> void write(ByteBuffer src,
long position,
A attachment,
CompletionHandler<Integer,? super A> handler);
我们可以看到,AIO 的读写主要也还是与 Buffer 打交道,这个与 NIO 是一脉相承的。
另外,还提供了用于将内存中的数据刷入到磁盘的方法:
public abstract void force(boolean metaData) throws IOException;
因为我们对文件的写操作,操作系统并不会直接针对文件操作,系统会缓存,然后周期性地刷入到磁盘。如果希望将数据及时写入到磁盘中,以免断电引发部分数据丢失,可以调用此方法。参数如果设置为 true,意味着同时也将文件属性信息更新到磁盘。
还有,还提供了对文件的锁定功能,我们可以锁定文件的部分数据,这样可以进行排他性的操作。
public abstract Future<FileLock> lock(long position, long size, boolean shared);
position 是要锁定内容的开始位置,size 指示了要锁定的区域大小,shared 指示需要的是共享锁还是排他锁
当然,也可以使用回调函数的版本:
public abstract <A> void lock(long position,
long size,
boolean shared,
A attachment,
CompletionHandler<FileLock,? super A> handler);
文件锁定功能上还提供了 tryLock 方法,此方法会快速返回结果:
public abstract FileLock tryLock(long position, long size, boolean shared)
throws IOException;
这个方法很简单,就是尝试去获取锁,如果该区域已被其他线程或其他应用锁住,那么立刻返回 null,否则返回 Fi