Java 4种IO模型

本文详细解析了四种IO模型:阻塞IO(BIO),非阻塞IO(NIO),IO多路复用(IOMultiplexing)和异步IO(AIO),并讨论了操作系统如何配置以支持百万级连接,重点介绍了Java NIO的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

1、IO读写的原理

2、4种IO模型

2.1、BIO(Blocking IO)

2.2、NIO(None Blocking IO)

2.3、IO多路复用(IO Multiplexin)(Java中的NIO)

2.4、AIO(Asynchronous IO)(异步IO模型)

3、操作系统配置支持百万连接


1、IO读写的原理

Linux系统中分为内核缓冲区和用户进程缓冲区。

外部设备的读写,会导致操作系统的中断,中断发生时,操作系统需要保存进程状态和数据等信息;中断恢复时,再恢复进程数据和状态。为了减少这种消耗,就有了缓冲区。

我们常说的IO读写,是在内核缓冲区和进程缓冲区之间的读写。

内核缓冲区是操作系统自带的,当程序要读硬盘的数据时,数据先从硬盘读到内核缓冲区,再从内核缓冲区复制到用户缓冲区(程序内存)。

系统调用读写的流程:

 

2、4种IO模型

2.1、BIO(Blocking IO)

阻塞IO,在Java中最常见的IO操作,就是阻塞IO,当IO读写时,线程是阻塞的。

Java发起IO读操作时,线程开始阻塞,操作系统等待数据进入内核缓冲区,等数据到达后(socket),把数据复制进内核缓冲区,当内核缓冲区满后,复制到用户缓冲区,这个过程中,线程一直是挂

起的。不会耗费cpu资源。

示例代码:

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BIOServer {

  public static void main(String[] args) throws Exception {
    ServerSocket serverSocket = new ServerSocket(6666);
    ExecutorService executorService = Executors.newCachedThreadPool();
    while (true) {
      System.out.println("等待客户端连接。。。。");
      //阻塞
      Socket socket = serverSocket.accept();
      executorService.execute(() -> {
        try {
          InputStream inputStream = socket.getInputStream(); //阻塞 
          byte[] bytes = new byte[1024];
          while (true) {
            int length = inputStream.read(bytes);
            if (length == -1) {
              break;
            }
            System.out.println(new String(bytes, 0, length, "UTF-8"));
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      });
    }
  }
}

2.2、NIO(None Blocking IO)

socket连接,默认是阻塞的。在Linux系统下,可以设置socket连接为非阻塞的。

当内核缓冲区还没有数据时,系统调用立刻返回一个调用失败的信息,不会阻塞。所以需要程序不断轮询查询,耗费大量cpu资源。

当内核缓冲区有数据并开始向用户缓冲区复制时,程序是阻塞的。

这种模型很少很少使用。不适合高并发场景下。

2.3、IO多路复用(IO Multiplexin)(Java中的NIO)

Java中的NIO(New IO),其实就是应用的IO多路复用模型。

需要操作系统支持select/epoll模式,把多个socket连接,注册到选择器上(Java中的Selector)。在Linux系统中,对应的系统调用为select/epoll系统调用。通过该系统调用,一个进程可以监视多个文

件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核能够将就绪的状态返回给应用程序

一个线程不断轮询查询选择器,返回可读/可写的socket列表(这个过程是阻塞的,但是一个线程可处理多个 soccer 连接);交给另一个进程读写,内核开始复制数据到用户缓冲区。(这个过程也是阻塞的)

示例代码:

import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class SelectorDemo {

  /**
   * 注册事件 *
   *
   * @return
   */
  private Selector getSelector() throws Exception { //获取selector对象
    Selector selector = Selector.open();
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    //非阻塞
    serverSocketChannel.configureBlocking(false);
    //获取通道并且绑定端口
    ServerSocket socket = serverSocketChannel.socket();
    socket.bind(new InetSocketAddress(6677));
    //注册感兴趣的事件
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    return selector;
  }

  public void listen() throws Exception {
    Selector selector = this.getSelector();
    while (true) {
      selector.select(); //该方法会阻塞,直到至少有一个事件的发生
      Set<SelectionKey> selectionKeys = selector.selectedKeys();
      Iterator<SelectionKey> iterator = selectionKeys.iterator();
      while (iterator.hasNext()) {
        SelectionKey selectionKey = iterator.next();
        process(selectionKey, selector);
        iterator.remove();
      }
    }
  }


  private void process(SelectionKey key, Selector selector) throws Exception {
    //新连接请求
    if (key.isAcceptable()) {
      ServerSocketChannel server = (ServerSocketChannel) key.channel();
      SocketChannel channel = server.accept();
      //非阻塞
      channel.configureBlocking(false);
      channel.register(selector, SelectionKey.OP_READ);
      //读数据
    } else if (key.isReadable()) {
      SocketChannel channel = (SocketChannel) key.channel();
      ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
      channel.read(byteBuffer);
      System.out.println("form 客户端 " + new String(byteBuffer.array(), 0, byteBuffer.position()));
    }
  }

  public static void main(String[] args) throws Exception {
    new SelectorDemo().listen();
  }
}

2.4、AIO(Asynchronous IO)(异步IO模型)

这是真正的异步IO。程序通过系统调用,向内核注册某个IO操作,内核拿到数据并复制到用户缓冲区后,再通知程序(发送某个信号或者回调程序的某个接口).

优点是完全的异步,不需要程序阻塞。

缺点是这个模型,需要操作系统支持才行。

就目前而言,Windows系统下通过IOCP实现了真正的异步IO。而在Linux系统下,异步IO模型在2.6版本才引入,目前并不完善,其底层实现仍使用epoll,与IO多路复用相同,因此在性能上没有明显的优势。

所以大多数的高并发服务器端的程序,一般都是基于Linux系统的。因而,目前这类高并发网络应用程序的开发,大多采用IO多路复用模型。netty使用的就是IO多路复用模型。

3、操作系统配置支持百万连接

在Linux系统中,单个进程能同时打开的句柄数是1024,远远不能满足要求。可通过以下配置进行调整:

在 /etc/rc.local 开机启动文件中增加

ulimit -SHn 1000000

-S是软性极限值(soft);-H是硬性极限值(hard);

也可以修改 /etc/security/limits.conf 文件来配置软硬极限值

soft nofile 1000000
hard nofile 1000000

参考:《Netty、Redis、Zookeeper 高并发实战》

 

欢迎留言一起讨论技术

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值