写点什么

IO 和 NIO 的对比篇

发布于: 2021 年 01 月 15 日

当遇到并发服务场景时,我们可以采取如下措施:




一、同步阻塞 IO 实现


public class DemoServer extends Thread {    private ServerSocket serverSocket;

public int getPort() { return serverSocket.getLocalPort(); }

public void run() { try { serverSocket = new ServerSocket(0); while (true) { // 非常占用内存资源,每个客户端启用一个线程,十分不合理 Socket socket = serverSocket.accept(); RequesHandler requesHandler = new RequesHandler(socket); requesHandler.start(); } } catch (IOException e) { e.printStackTrace(); } finally { if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } ; } } }

public static void main(String[] args) throws IOException { DemoServer server = new DemoServer(); server.start(); try (Socket client = new Socket(InetAddress.getLocalHost(), server.getPort())) { BufferedReader buferedReader = new BufferedReader(new InputStreamReader(client.getInputStream())); buferedReader.lines().forEach(s -> System.out.println(s)); } }}

// 简化实现,不做读取,直接发送字符串class RequesHandler extends Thread { private Socket socket;

RequesHandler(Socket socket) { this.socket = socket; }

@Override public void run() { try (PrintWriter out = new PrintWriter(socket.getOutputStream());) { out.println("Hello world!"); out.flush(); } catch (Exception e) { e.printStackTrace(); } }}
复制代码


每次 new 一个线程或者销毁一个线程是有明显的开销的,每个线程都有单独的线程结构,非常占用内存资源,每个客户端启用一个线程是十分不合理的, 因此可以采用线程池的方式进行优化。


// 也是阻塞IO,采用线程池的方式处理请求,当来一个新的客户端连接时,// 将请求 Socket 封装成一个 task ,放到线程池中取执行。

serverSocket = new ServerSocket(0);executor = Executors.newFixedThreadPool(8);while (true) { Socket socket = serverSocket.accept(); RequesHandler requesHandler = new RequesHandler(socket); executor.execute(requesHandler);}
复制代码




二、NIO 实现


NIO(非阻塞 IO) 多路复用机制



public class NIOServer extends Thread {    public void run() {        try (Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open();) {// 创建Selector和Channel            serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));            serverSocket.configureBlocking(false);            // 注册到Selector,并说明关注点            serverSocket.register(selector, SelectionKey.OP_ACCEPT);            while (true) {                selector.select();// 阻塞等待就绪的Channel,这是关键点之一                Set<SelectionKey> selectedKeys = selector.selectedKeys();                Iterator<SelectionKey> iter = selectedKeys.iterator();                while (iter.hasNext()) {                    SelectionKey key = iter.next();                    // 生产系统中一般会额外进行就绪状态检查                    sayHelloWorld((ServerSocketChannel) key.channel());                    iter.remove();                }            }        } catch (IOException e) {            e.printStackTrace();        }    }

private void sayHelloWorld(ServerSocketChannel server) throws IOException { try (SocketChannel client = server.accept();) { ByteBuffer readBuffer = ByteBuffer.allocate(32); client.read(readBuffer); System.out.println("Server received : " + new String(readBuffer.array())); ByteBuffer writeBuffer = ByteBuffer.allocate(128); writeBuffer.put("hello xiaoming".getBytes()); writeBuffer.flip(); client.write(writeBuffer); //client.write(Charset.defaultCharset().encode("Hello world!")); } }

public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.start(); try { SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8888));

ByteBuffer writeBuffer = ByteBuffer.allocate(32); ByteBuffer readBuffer = ByteBuffer.allocate(32);

writeBuffer.put("hello".getBytes()); writeBuffer.flip();

while (true) { writeBuffer.rewind(); socketChannel.write(writeBuffer);// readBuffer.clear(); socketChannel.read(readBuffer); System.out.println("Client received : " + new String(readBuffer.array())); } } catch (IOException e) { } }

/** * @return */private int getPort() { return 8888;}
复制代码


三、总结

在前面两个例子中:阻塞 IO 和伪异步 IO,一个是使用 new 线程的方式,另外一个是采用线程池管理的方式, IO 都是同步阻塞模式,所以需要多线程以实现多任务处理。


而 NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。


用户头像

还未添加个人签名 2020.09.07 加入

还未添加个人简介

评论

发布
暂无评论
IO和NIO的对比篇