3.4.3 异步I/O通道

NIO. 2中引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。这两种异步通道在功能上类似于前面介绍过的一般通道,只是对应功能的使用方式不相同。对于文件通道来说,一般的操作,如读取和写入,都是同步进行的,调用者会处于阻塞状态,以等待相应操作的完成。而对于套接字通道来说,阻塞式套接字通道的使用方式与文件通道相同,而非阻塞式套接字通道的使用方式则依靠选择器来完成。异步通道一般提供两种使用方式:一种是通过Java同步工具包中的java.util.concurrent.Future类的对象来表示异步操作的结果;另外一种是在执行操作时传入一个java.nio.channels.CompletionHandler接口的实现对象作为操作完成时的回调方法。这两种使用方式的区别只在于调用者通过何种方式来使用异步操作的结果。在使用Future类的对象时,要求调用者在合适的时机显式地通过Future类的对象的get方法来得到实际的操作结果;而在使用CompletionHandler接口时,实际的调用结果作为回调方法的参数来给出。

下面先通过一个异步文件通道来介绍使用Future类的对象的做法。异步文件通道由java.nio.channels.AsynchronousFileChannel类来表示。如代码清单3-26所示,打开一个异步文件通道的方式与使用FileChannel类的做法相似,也是通过open方法来完成的。对文件通道的读取和写入也是通过对应的read和write方法来完成的。所不同的是read和write方法要么返回一个Future类的对象,要么要求传入一个CompletionHandler接口的实现对象作为回调方法。这里用的是write方法返回的Future类的对象。在调用write方法之后,程序可以执行其他的操作,然后再调用Future类的对象的get方法来获取write操作的执行结果。如果操作执行成功,get方法会返回实际写入的字符数;如果执行失败,会抛出java.util.concurrent.ExecutionException异常。

代码清单3-26 向异步文件通道中写入数据的示例


public voidasync Write()throws IO Exception, Execution Exception,

InterruptedException{

AsynchronousFileChannel channel=AsynchronousFileChannel.open(Paths.get("large.bin"),StandardOpenOption.CREATE, StandardOpenOption.WRITE);

ByteBuffer buffer=ByteBuffer.allocate(3210241024);

Future<Integer>result=channel.write(buffer,0);

//其他操作

Integer len=result.get();

}


这里需要注意的是,异步文件通道并不支持FileChannel类所提供的相对读写操作。在异步文件通道中并没有当前读写位置的概念,因此所有的read和write方法在调用时都必须显式地指定读写操作的位置。

异步套接字通道AsynchronousSocketChannel和AsynchronousServerSocketChannel类分别对应一般的SocketChannel和ServerSocketChannel类。代码清单3-27给出了使用AsynchronousServerSocketChannel类和CompletionHandler接口的示例。与ServerSocketChannel类相同的是,accept方法用来接受来自客户端的连接。不过,当有新连接建立时会调用CompletionHandler接口实现对象中的completed方法;当出现错误时,会调用failed方法。值得一提的是accept方法的第一个参数,该参数可以是一个任意类型的对象,称为调用时的“附件对象”。附件对象在accept方法调用时传入,可以在CompletionHandler接口的实现对象中从completed和failed方法的参数中获取,这样就可以进行数据的传递。使用CompletionHandler接口的方法都支持使用附件对象来传递数据。

代码清单3-27 异步套接字通道的使用示例


public void startAsyncSimpleServer()throws IOException{

Asynchronous Channel Groupgroup=Asynchronous Channel Group.withFixedThreadPool(10,Executors.defaultThreadFactory());

final AsynchronousServerSocketChannel serverChannel=

Asynchronous Server Socket Channel.open(group).bind(new InetSocketAddress(10080));

serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>(){

public void completed(AsynchronousSocketChannel clientChannel, Void attachement){

serverChannel.accept(null, this);

//使用clientChannel

}

public void failed(Throwable throwable, Void attachement){

//错误处理

}

});

}


异步通道在处理I/O请求时,需要使用一个java.nio.channels.AsynchronousChannel-Group类的对象。AsynchronousChannelGroup类的对象表示的是一个异步通道的分组,每一个分组都有一个线程池与之关联,这个线程池中的线程用来处理I/O事件。多个异步通道可以共享一个分组的线程池资源。调用AsynchronousSocketChannel和AsynchronousServerSocketChannel类的open方法打开异步套接字通道时,可以传入一个AsynchronousChannelGroup类的对象作为所使用的分组。如果调用open方法时没有传入AsynchronousChannelGroup类的对象,默认使用系统提供的分组。需要注意的是,系统分组对应的线程池中的线程是守护线程。如果代码清单3-27中没有显式使用AsynchronousChannelGroup类的对象,程序启动之后会很快退出,因为系统分组使用的守护线程不会阻止虚拟机退出。

创建AsynchronousChannelGroup类的对象需要使用AsynchronousChannelGroup类中的静态工厂方法withFixedThreadPool、withCachedThreadPool或withThreadPool。创建出来的AsynchronousChannelGroup类对象需要被显式关闭,否则虚拟机不会退出。关闭的方式类似于停止java.util.concurrent.ExecutorService接口表示的任务执行服务的做法。第11章将对ExecutorService接口进行详细介绍,可以参考相应的关闭方式来关闭AsynchronousChannelGroup类的对象。