IO模型
java共支持3中IO模型:BIO,NIO,AIO。下面来总结一下三种模型。
BIO
BIO(Blocking IO): 同步阻塞IO。
代码
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9000);
System.out.println("BIOServer等待连接。。");
//阻塞方法 等待客户端的链接
Socket clientSocket = serverSocket.accept();
System.out.println("BIOServer有客户端连接了。。");
byte[] bytes = new byte[1024];
System.out.println("准备read。。");
//阻塞方法 等待客户端写数据,没有数据可读时就阻塞
int read = clientSocket.getInputStream().read(bytes);
System.out.println("read完毕。。");
if (read != -1) {
System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
}
clientSocket.getOutputStream().write("HelloClient".getBytes());
clientSocket.getOutputStream().flush();
}
上述代码中,serverSocket.accept()和clientSocket.getInputStream().read(bytes)方法是阻塞的,如果多个客服端线程来连接服务端,就会造成阻塞,降低性能。如果使用多线程来解决性能问题,来多少个客户端开多少个线程,显然是不合适的,服务端压力也会加大。因此,BIO会有以下缺点:
- IO代码里的accept和read操作是阻塞操作,会造成线程阻塞,浪费资源;
- 如果线程过多,会导致服务端压力过大。
NIO
NIO(Non Blocking IO): 同步非阻塞,从jdk1.4开始支持,主要有三大组件,channel,selector,buffer。
NIO三大组件:
- channel(通道)
- 类似于流,是双向的,一个流只可能是InputStream或是OutputStream,但是channel是双向的;
- selector(选择器)
- channel会注册到selector上,由selector根据channel的读写事件交给空闲的线程处理;
- buffer(缓冲区)
- 所有数据的读写都是通过buffer来进行的,java中的原生8种基本数据类型都有各自对应的buffer类型,(除Boolean外),如IntBuffer,CharBuffer,ByteBuffer,LongBuffer,ShortBuffer。
代码
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false); //设置非阻塞模式
serverSocketChannel.bind(new InetSocketAddress(9090));//绑定9090端口
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //将selector注册到serverSocketChannel
while (true) {
selector.select();//此方法为阻塞方法
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey selectionKey = keyIterator.next();
if (selectionKey.isValid()) { //此键是否有效
if (selectionKey.isAcceptable()) { //连接事件
ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverSocketChannel1.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (selectionKey.isReadable()) { //读取数据
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
System.out.println(new String(buffer.array()));
ByteBuffer buffer1 = ByteBuffer.wrap(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()).getBytes());
socketChannel.write(buffer1);
}
}
keyIterator.remove();//移除key,避免重复消费
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
步骤:
- 开启ServerSocketChannel,绑定9090端口,并设置为非阻塞模式;
- 开始Selector,并将ServerSocketChannel注册到Selector,监听连接事件;
- 调用select()方法,是否有事件进来,如果有,则获取并遍历SelectionKey,判断其为连接事件还是读取事件,并做对应的操作,做完相应操作之后移除key,避免重复消费。
总结
NIO是基于epoll基于事件响应机制来优化NIO。Redis也是典型的基于epoll的NIO线程模型(nginx也是),epoll实例收集所有事件(连接与读写事件),由一个服务端线程连续处理所有事件
命令,所以redis目前是单线程的。
AIO
异步非阻塞:适用于连接数目多且连接比较长(重操作)的架构。
用法
AsynchronousServerSocketChannel与NIO中ServerSocketChannel用法基本一致,AsynchronousSocketChannel与SocketChannel用法基本一致,只不过是在CompletionHandler的回调completed方法中处理逻辑。