Java NIO介绍(二)————无堵塞io和Selector简单介绍

2023-11-14

无堵塞IO介绍
既然NIO相比于原来的IO在读取速度上其实并没有太大区别(因为NIO出来后,IO的低层已经以NIO为基础重新实现了),那么NIO的优点是什么呢?
NIO是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,而且已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
传统的IO模型
让我们先回忆一下传统的服务器端同步阻塞I/O处理(也就是BIO, Blocking I/O)的经典编程模型:
 ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池

 ServerSocket serverSocket = new ServerSocket();
 serverSocket.bind(8088);
 while(!Thread.currentThread.isInturrupted()){//主线程死循环等待新连接到来
 Socket socket = serverSocket.accept();
 executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程
}

class ConnectIOnHandler extends Thread{
    private Socket socket;
    public ConnectIOnHandler(Socket socket){
       this.socket = socket;
    }
    public void run(){
      while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循环处理读写事件
          String someThing = socket.read()....//读取数据
          if(someThing!=null){
             ......//处理数据
             socket.write()....//写数据
          }

      }
    }

这是一个经典的每连接每线程的模型,之所以使用多线程,主要原因在于socket.accept()、socket.read()、socket.write()三个主要函数都是同步阻塞的,当一个连接在处理I/O的时候,系统是阻塞的,如果是单线程的话必然就挂死在那里;但CPU是被释放出来的,开启多线程,就可以让CPU去处理更多的事情。
其实这也是所有使用多线程的本质:
1、利用多核。
2、当I/O阻塞系统,但CPU空闲的时候,可以利用多线程使用CPU资源。
现在的多线程一般都使用线程池,可以让线程的创建和回收成本相对较低。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的I/O并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。
不过,这个模型最本质的问题在于,严重依赖于线程。但线程是很"贵"的资源,主要表现在:
1、线程的创建和销毁成本很高,在Linux这样的操作系统中,线程本质上就是一个进程。创建和销毁都是重量级的系统函数。
2、线程本身占用较大内存,像Java的线程栈,一般至少分配512K~1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。
3、线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统load偏高、CPU sy使用率特别高(超过20%以上),导致系统几乎陷入不可用的状态。
4、容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。
所以,当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。随着移动端应用的兴起和各种网络游戏的盛行,百万级长连接日趋普遍,此时,必然需要一种更高效的I/O处理模型。
NIO模型(Reactor轮询模式)
回忆BIO模型,之所以需要多线程,是因为在进行I/O操作的时候,一是没有办法知道到底能不能写、能不能读,只能"傻等",即使通过各种估算,算出来操作系统没有能力进行读写,也没法在socket.read()和socket.write()函数中返回,这两个函数无法进行有效的中断。所以除了多开线程另起炉灶,没有好的办法利用CPU。
NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:
如果一个连接不能读写(socket.read()返回0或者socket.write()返回0),我们可以把这件事记下来,记录的方式通常是在 Selector上注册标记位,然后切换到其它就绪的连接(channel)继续进行读写。
下面具体看下如何利用事件模型单线程处理所有I/O请求:
NIO的主要事件有几个:读就绪、写就绪、有新连接到来。
用一个死循环选择就绪的事件,会执行系统调用,还会阻塞的等待新事件的到来。新事件到来的时候,会在selector上注册标记位,标示可读、可写或者有连接到来。
注意,select是阻塞的,无论是通过操作系统的通知还是不停的轮询,这个函数是阻塞的。所以你可以放心大胆地在一个while(true)里面调用这个函数而不用担心CPU空转。
服务端代码:
package nio3;
import java.io.IOException;
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 NIOServer {
	
	private  int flag = 0;
	private  int BLOCK = 2048;
	private  ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
	private  ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
	private  Selector selector;

	public NIOServer(int port) throws IOException {
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		serverSocketChannel.configureBlocking(false);
		ServerSocket serverSocket = serverSocketChannel.socket();
		serverSocket.bind(new InetSocketAddress(port));
		selector = Selector.open();
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		System.out.println("Server Start----8888:");
	}

	private void listen() throws IOException {
		//轮询  事件驱动模式
		while (true) {
			// select()阻塞,等待有事件发生唤醒 
			selector.select();
			Set<SelectionKey> selectionKeys = selector.selectedKeys();
			Iterator<SelectionKey> iterator = selectionKeys.iterator();
			while (iterator.hasNext()) {
				SelectionKey selectionKey = iterator.next();
				//处理事件后,要移除
				iterator.remove();
				handleKey(selectionKey);
			}
		}
	}

	/**
	 * 处理不同事件
	 */
	private void handleKey(SelectionKey selectionKey) throws IOException {
		ServerSocketChannel server = null;
		SocketChannel client = null;
		String receiveText;
		String sendText;
		int count=0;
		//连接事件
		if (selectionKey.isAcceptable()) {
			server = (ServerSocketChannel) selectionKey.channel();
			client = server.accept();
			client.configureBlocking(false);
			client.register(selector, SelectionKey.OP_READ);
			//读取模式
		} else if (selectionKey.isReadable()) {
			client = (SocketChannel) selectionKey.channel();
			receivebuffer.clear();
			count = client.read(receivebuffer);	
			if (count > 0) {
				receiveText = new String( receivebuffer.array(),0,count);
				System.out.println("读取到:"+receiveText);
				//注册对写的事件感兴趣
				client.register(selector, SelectionKey.OP_WRITE);
			}
			//写模式
		} else if (selectionKey.isWritable()) {
			sendbuffer.clear();
			client = (SocketChannel) selectionKey.channel();
			sendText="message from server--" + flag++;
			sendbuffer.put(sendText.getBytes());
			sendbuffer.flip();
			client.write(sendbuffer);
			System.out.println("向客户端发送了"+sendText);
//			client.register(selector, SelectionKey.OP_READ);
			client.close();
		}
	}

	public static void main(String[] args) throws IOException {
		NIOServer server = new NIOServer(9000);
		server.listen();
	}
}

客户端代码(用普通的io写的):
package nio;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;

public class MultiPortEcho2 {
	
	public static void main(String[] args) throws Exception{
		Socket socket = new Socket("localhost", 9000);
		
		DataOutputStream sendStream =  new DataOutputStream( socket.getOutputStream());
		DataInputStream receiveStream =  new DataInputStream( socket.getInputStream());
		System.out.println(receiveStream.available());
		sendStream.write("aasdaaa".getBytes());
		int line=-1;
		byte[] bytes=new byte[1024];
		while((line=receiveStream.read(bytes))!=-1){
			System.out.println(new String(bytes,0,line));
		}
		socket.close();
	}
	
}


以上代码是用了一个线程来处理不同的事件,当然我们可以在接受到读事件或者写时间的时候开启多线程来进行读写的过程(在轮询的时候已经确定都或者写模式,因此在新开的线程中只是简单的io读写操作,不会堵塞)。
优化线程模型
单线程处理I/O的效率确实非常高,没有线程切换,只是拼命的读、写、选择事件。但现在的服务器,一般都是多核处理器,如果能够利用多核心进行I/O,无疑对效率会有更大的提高。
仔细分析一下我们需要的线程,其实主要包括以下几种:
1、事件分发器,单线程选择就绪的事件。
2、I/O处理器,包括connect、read、write等,这种纯CPU操作,一般开启CPU核心个线程就可以。
3、业务线程,在处理完I/O后,业务一般还会有自己的业务逻辑,有的还会有其他的阻塞I/O,如DB操作,RPC等。只要有阻塞,就需要单独的线程。
另外连接的处理和读写的处理通常可以选择分开,这样对于海量连接的注册和读写就可以分发。虽然read()和write()是比较高效无阻塞的函数,但毕竟会占用CPU,如果面对更高的并发则无能为力。
参考资料:Java NIO浅析 来自:美团点评技术团队(微信号:meituantech)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java NIO介绍(二)————无堵塞io和Selector简单介绍 的相关文章

随机推荐

  • webgl学习之路(三)——透视投影矩阵的推导过程

    关于透视投影矩阵的讲解 网上有不少教程 但是有一点大家基本上都没有讲清楚 就是z轴坐标 这里的Z轴相当于景深 的推导过程 基本上是一笔带过 下面先从头开始讲推导过程 再慢慢说Z轴的推导过程 透视投影如下图 透视投影的过程如下 所观察的物体在
  • 前端实现拖拽效果改变元素顺序

    文章目录 前言 一 实现效果 二 拖拽API 1 代码 2 遇见问题 总结 前言 在一次工作中 前端要实现通过鼠标实现拖拽改变顺序的功能 之前没有接触过拖拽这一块所以刚开始一筹莫展 幸运的是在查阅学习中实现了前端拖拽功能 一 实现效果 二
  • python对离散功率点进行积分得到电耗

    data pd read csv r C Users EDY Desktop csv data from scipy integrate import trapz scipy integrate trapz y x scipy integr
  • PCL 将对象模板与点云对齐

    目录 一 算法概述 二 代码实现 三 结果展示 四 相关链接 五 实验数据 一 算法概述 这是PCL官网给出的一个模版匹配教程 用来说明如何将其他教程中介绍的一些工具结合起来解决一个更高层次的问题 即将以前捕获的对象模型与一些新捕获的数据对
  • 红黑树(算法导论版)

    1 定义 1 每个节点是红色或者黑色的 2 根节点是黑色的 3 所有叶子结点 NIL 都是黑色的 4 如果一个节点是红色 则它的两个子节点都是黑色的 5 对每个节点 从该节点到其所有后代叶节点的简单路径上 均包含相同数目的黑色节点 2 性质
  • 飞行汽车比无人驾驶更早到来?清华猛狮团队研制出陆空两栖自主驾驶飞车

    清华猛狮无人驾驶实验室一年前启动无人驾驶飞车研制项目 一年时间内 第一代研发样机已在河北清华发展研究院固安分院与延庆山区试飞成功 了解无人车挑战赛的人 对清华猛狮无人智能车团队并不陌生 它是由清华大学车辆与运载学院和清华大学计算科学与技术系
  • Yarn介绍及快速安装 - Debian/Ubuntu Linux

    1 Yarn介绍 Yarn 是一个用于管理 JavaScript 包的快速 可靠和安全的包管理器 它是由 Facebook Google Exponent 和 Tilde 团队共同开发的 旨在提供比 npm 更快速 可靠的包管理体验 以下是
  • IBM小型机(AIX)技术手册(一)

    2007年7月 2008年7月在北京中软国际的工作时总结的IBM小型机技术手册 AIX基本命令 创建文件夹 Mkdir 名称 查看硬件信息 prtconf 查看卷组 lsvg o 查看进程 ps ef grep 名称 如 socket等 查
  • python模拟鼠标拖动滑块_Python+Selenium 拖动滑块 (一)

    在我们登录账号中常常会遇到各种验证码 如图片验证码 拖动滑块验证 滑块验证码只需要用户使用鼠标将滑块从某个位置拖动到另一个位置即可 程序通过记录用户拖动滑块的轨迹 这一串的轨迹数据采用模式识别的手段就可以判断出这是否是真人在操作 滑块验证通
  • 中标麒麟 docker安装及运行第一个实例

    一 下载安装包 选择适合版本 本次安装选择20 10 7 Index of linux static stable x86 64 二 安装 1 将下载的安装文件进行解压 命令如下 tar xzf docker 20 10 7 tar 2 将
  • 【Linux内核】cpu时间片的概念

    推荐阅读 浅谈linux 内核网络 sk buff 之克隆与复制 深入linux内核架构 进程 线程 了解Docker 依赖的linux内核技术 cpu时间片的概念 时间片即CPU分配给各个程序的时间 每个线程被分配一个时间段 称作它的时间
  • 【mcuclub】PTC加热模块

    一 实物图 名称 PTC加热片 工作电压 5V 最大温度 180 功率 7 12W 是否防水 不防水 外部加热 名称 加热棒 工作电压 5V 最大温度 110 功率 7 10W 是否防水 防水 可直接放入水中 二 简介 PTC电加热器是一种
  • Linux命令自动补齐oh-my-zsh插件及美化主题超详细

    安装zsh 1 查看系统当前使用的shell echo SHELL 2 查看系统是否安装了zsh cat etc shells 3 用yum安装zsh yum y install zsh 4 查看shell列表 cat etc shells
  • [附源码]计算机毕业设计Python游戏交易平台(程序+源码+LW文档)

    该项目含有源码 文档 程序 数据库 配套开发软件 软件安装教程 项目运行 环境配置 Pychram社区版 python3 7 7 Mysql5 7 HBuilderX list pip Navicat11 Django nodejs 项目技
  • C#中Socket的Accept()和BeginAccept()的区别

    C 中Socket的Accept 和BeginAccept 的区别 区别在于 Accept 是同步的 BeginAccept 是异步的 调用accept 或者BeginAccept 函数来接受客户端的连接 这就可以和客户端通信了 Begin
  • 五分钟学会一门编程语言?

    大家好 我是可乐 看到标题 不出意外的话 你肯定开始骂我了 标题党 什么编程语言五分钟就能学会 其实我本来也是不相信的 但是学过了才知道这是真的 1 Brainfuck 看到这个小标题 不要误会 我没有骂人 这就是今天文章的主人公 也就是让
  • 如何理解BIO、NIO、AIO的区别

    一 同步阻塞I O BIO 同步阻塞I O 服务器实现模式为一个连接一个线程 即客户端有连接请求时服务器就需要启动一个线程进行处理 如果这个连接不做任何事情会造成不必要的线程开销 可以通过线程池机制来改善 BIO方式适用于连接数目比较小且固
  • Leetcode 124. 二叉树中的最大路径和

    题目内容 给定一个非空二叉树 返回其最大路径和 本题中 路径被定义为一条从树中任意节点出发 沿父节点 子节点连接 达到任意节点的序列 该路径至少包含一个节点 且不一定经过根节点 示例 1 输入 1 2 3 1 2 3 输出 6 示例 2 输
  • 使用 js 实现 贷款计算器功能

    table tr th Enter Loan Data th td td th Loan Balance Interest Payments th tr tr td Amount of Loan td tr table
  • Java NIO介绍(二)————无堵塞io和Selector简单介绍

    无堵塞IO介绍 既然NIO相比于原来的IO在读取速度上其实并没有太大区别 因为NIO出来后 IO的低层已经以NIO为基础重新实现了 那么NIO的优点是什么呢 NIO是一种同步非阻塞的I O模型 也是I O多路复用的基础 而且已经被越来越多地