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简单介绍 的相关文章

  • 纯 Fortran 过程中的 I/O

    我正在尝试将错误检查合并到我正在编写的纯过程中 我想要这样的东西 pure real function func1 output unit a implicit none integer a output unit if a lt 0 th
  • java IO将一个文件复制到另一个文件

    我有两个 Java io File 对象 file1 和 file2 我想将 file1 的内容复制到 file2 有没有一种标准方法可以做到这一点 而无需我创建一个读取 file1 并写入 file2 的方法 不 没有内置方法可以做到这一
  • Java 7 watchservice获取文件更改偏移量

    我刚刚尝试使用 Java 7 WatchService 来监视文件的更改 这是我敲出的一些代码 WatchService watcher FileSystems getDefault newWatchService Path path Pa
  • 为什么 .NET 异步等待文件复制比同步 File.Copy() 调用消耗更多 CPU?

    为什么下面的代码会产生 public static class Program public static void Main params string args var sourceFileName C Users ehoua Desk
  • 修饰符 async 对此项目无效

    这似乎并不是数百个具有相同错误的其他问题的重复 我把它们都看过了 发现它们是无关的 我正在制作一个小笔记应用程序 并尝试从目录中读取文件 按照 MSDN 示例 我有以下代码 但它给了我一个错误 错误 1 修饰符 async 对此无效 项目
  • 如何从标准输入读取一行,阻塞直到找到换行符?

    我试图从命令行的标准输入一次读取任意长度的一行 我不确定是否能够包含 GNU readline 并且更喜欢使用库函数 我读过的文档表明getline应该可以工作 但在我的实验中它不会阻塞 我的示例程序 include
  • Haskell 输入返回元组

    我想知道 IO 函数是否可以返回元组 因为我想从这个函数中获取这些元组作为另一个函数的输入 investinput IO gt Char Int investinput do putStrLn Enter Username username
  • 如何使用 open with 语句打开文件

    我正在研究如何在 Python 中进行文件输入和输出 我编写了以下代码 将一个文件中的名称列表 每行一个 读取到另一个文件中 同时根据文件中的名称检查名称并将文本附加到文件中出现的位置 该代码有效 可以做得更好吗 我想用with open
  • 替换文件中的字符串

    我正在寻找一种方法来替换文件中的字符串而不将整个文件读入内存 通常我会使用 Reader 和 Writer 即如下所示 public static void replace String oldstring String newstring
  • 在 Haskell 中调试时打印时间戳

    我仍在学习 Haskell 并调试一些函数 并且通常有一个时间戳函数来了解某些操作何时开始和停止 doSomeAction String gt IO doSomeAction arg1 do putStrLn lt lt makeTime
  • 如何使用C++以相反的顺序读取文件[重复]

    这个问题在这里已经有答案了 如何使用 C 以相反的顺序 即从 eof 读取文本文件 是的 但基本上你必须手动完成 基本算法如下 查找文件末尾is seekg 0 is end 确定文件大小is tellg 反复向后查找并读取文件块 直到到达
  • jQuery 单属性、带过滤器的多值选择器

    Images var boxlinks a href filter href png href gif href jpg href jpeg 有没有更有效的方法来使用 jQuery 中的过滤器选择单个属性的多个值 这里我尝试仅选择带有图像作
  • phonegap html5 android 同步文件系统 IO

    如何使用 PhoneGaps 文件系统 API 同步读写文件 有可用的同步包装器吗 无法通过提供的 api 同步访问文件 从phonegap的实现方式猜测 我怀疑您是否可以编写一个插件来同步执行此操作
  • 如何在 python 中更新/修改 XML 文件?

    我有一个 XML 文档 我想在它包含数据后对其进行更新 我考虑过打开 XML 文件 a 追加 模式 问题是新数据将写入根结束标记之后 如何删除文件的最后一行 然后从该点开始写入数据 然后关闭根标签 当然 我可以读取整个文件并进行一些字符串操
  • 通过子类化 `io.TextIOWrapper` 来子类化文件 - 但它的构造函数有什么签名?

    我正在尝试子类化io TextIOWrapper下列的这个帖子 https stackoverflow com a 23796737 974555 虽然我的目标不同 以此开始 注意 动机 https stackoverflow com a
  • 信号处理程序内的格式化 I/O

    我想编写一个 SIGSEGV 处理程序 将消息写入文件 FILE 我听说 fprintf 不可重入 不应在信号处理程序内调用 是否有它的可重入版本 或者任何其他提供可以在信号处理程序内部调用的格式化文件 I O 的函数 否 根据C11标准N
  • 谁能解释一下 GHC 对 IO 的定义吗?

    标题非常自我描述 但有一个部分引起了我的注意 newtype IO a IO State RealWorld gt State RealWorld a 剥离newtype 我们得到 State RealWorld gt State Real
  • 在 haskell 中处理 IO 与纯代码

    我正在编写一个shell脚本 我在haskell中的第一个非示例 它应该列出一个目录 获取每个文件大小 进行一些字符串操作 纯代码 然后重命名一些文件 我不确定我做错了什么 所以有两个问题 我应该如何安排这样的程序中的代码 我有一个具体问题
  • java.nio TransferTo 似乎快得不可思议?

    有人可以解释一下如何transferTo方法可以以每秒 1000 MB 的速度复制文件 我使用 372MB 二进制文件运行了一些测试 第一个副本很慢 但如果我更改输出名称并再次运行它 输出目录中会在短短 180 毫秒内出现一个附加文件 结果
  • 从文件中获取InputStream,该文件可能位于(或不位于)类路径中[重复]

    这个问题在这里已经有答案了 只是想知道哪种方法是读取类路径中的文件的最佳方法 我唯一拥有的是带有文件路径的属性 举个例子 文件路径 类路径 com mycompany myfile txt 文件路径 文件 myfolder myfile t

随机推荐

  • 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多路复用的基础 而且已经被越来越多地