• 为什么NIO比BIO效率高


    目录

    BIO实例

    为什么BIO效率会低些

    主要对比:


     

    BIO实例

    服务端

    1. package com.wyh.study.Fake_Thread_IO;
    2. import java.io.IOException;
    3. import java.net.ServerSocket;
    4. import java.net.Socket;
    5. public class Fake_Server {
    6. public static void main(String[] args) {
    7. //1.注册端口
    8. try {
    9. ServerSocket ss = new ServerSocket(8888);
    10. /**
    11. *2.定义一个死循环,负责不断接收客户端Socket的连接请求
    12. */
    13. HandlerSocketServerPool pool = new HandlerSocketServerPool(3, 10);
    14. while(true){
    15. Socket socket = ss.accept();
    16. ServerRunnableTarget target = new ServerRunnableTarget(socket);
    17. pool.execute(target);
    18. }
    19. } catch (IOException e) {
    20. e.printStackTrace();
    21. }
    22. }
    23. }

     客户端

    1. package com.wyh.study.Fake_Thread_IO;
    2. import java.io.IOException;
    3. import java.io.PrintStream;
    4. import java.net.Socket;
    5. import java.util.Scanner;
    6. public class Fake_Client {
    7. public static void main(String[] args) {
    8. //1.请求与服务端相连
    9. try {
    10. Socket socket = new Socket("127.0.0.1", 8888);
    11. //2.得到打印流
    12. PrintStream ps = new PrintStream(socket.getOutputStream());
    13. //3.使用循环不断发送消息
    14. Scanner sc = new Scanner(System.in);
    15. while(true){
    16. System.out.println("客户端发送:");
    17. String msg = sc.nextLine();
    18. ps.println(msg);
    19. ps.flush();
    20. }
    21. } catch (IOException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. }

    线程池类

    1. /**
    2. * 线程处理类
    3. */
    4. public class HandlerSocketServerPool {
    5. //1.创建一个线程池成员变量存储一个线程池对象
    6. private ExecutorService executorService;
    7. /**
    8. * 2.初始化线程池对象
    9. * maxThreadNum:最大线程数
    10. */
    11. public HandlerSocketServerPool(int maxThreadNum,int queueSize) {
    12. executorService=new ThreadPoolExecutor(3,maxThreadNum,120, TimeUnit.SECONDS,new ArrayBlockingQueue(queueSize));
    13. }
    14. /**
    15. * 3.提供一个方法来提交任务target到线程池的任务队列来暂存,等待线程池处理
    16. */
    17. public void execute(Runnable target){
    18. executorService.execute(target);
    19. }
    20. }

    任务类

    1. package com.wyh.study.Fake_Thread_IO;
    2. import lombok.extern.slf4j.Slf4j;
    3. import java.io.BufferedReader;
    4. import java.io.IOException;
    5. import java.io.InputStream;
    6. import java.io.InputStreamReader;
    7. import java.net.Socket;
    8. /**
    9. * Socket任务类
    10. */
    11. @Slf4j(topic = "c.ServerRunnableTarget")
    12. public class ServerRunnableTarget implements Runnable{
    13. private Socket socket;
    14. /**
    15. * 1.争对客户端发送的信息socket
    16. * @param socket
    17. */
    18. public ServerRunnableTarget(Socket socket) {
    19. this.socket = socket;
    20. }
    21. @Override
    22. public void run() {
    23. //1.处理收到客户端socket通信请求
    24. try {
    25. InputStream is = socket.getInputStream();
    26. //2.将字节输入流进行封装
    27. BufferedReader br = new BufferedReader(new InputStreamReader(is));
    28. String msg;
    29. while((msg= br.readLine())!=null){
    30. log.debug("服务端收到消息:{}",msg);
    31. }
    32. } catch (IOException e) {
    33. e.printStackTrace();
    34. }
    35. }
    36. }

    为什么BIO效率会低些

    1.socketServer的accept()接收客户端的方法是阻塞的,如果连接通道没有数据传输,就白给了

    另外,一个线程调用read() 或write()时,该线程被阻塞,直到有数据被读取或者数据写入。该线程在阻塞期间不能做其他事情

    2.服务端监听取得socket后,将这个socket分给一个线程去处理。此时socket需要等待有效的请求数据到来后,才可以真正开始处理请求

    3.socket交给线程后,这时socketServer才可以接收下一个连接请求——>体现出了阻塞

    4.获得连接的顺序是和客户端请求到达服务器的先后顺序相关

    5.BIO是面向流的,NIO是面向缓冲区的。JavaIO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方(这是非常重要的)————>NIO的缓冲读取方法略有不同:数据读取到一个缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性;但是,还需要检查是否该缓冲区中包含所有需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据

    文件I/O的内核缓冲 - Yungyu - 博客园 (cnblogs.com)

    NIO:

    1.基于事件驱动,当有连接请求,会将此连接注册到多路复用器上(selector)。
    2.在多路复用器上可以注册监听事件,比如监听accept、read
    3.通过监听,当真正有请求数据时,才来处理数据。
    4不会阻塞,会不停的轮询是否有就绪的事件,所以处理顺序和连接请求先后顺序无关,与请求数.据到来的先后顺序有关
     

    主要对比:

    1.一个线程处理一个请求
    BIO:连接请求来,建立socket,等待请求数据到来(t1),处理时间(t2)
    NIO:连接请求来,注册到selector,设置读监听,等待请求数据(t1),处理时间(t2)
    此时,两者用时皆为t1+t2,没有区别
    2.一个线程处理两个请求
    第一个请求,等待请求数据(10),处理时间(1)
    第二个请求,等待请求数据(1),处理时间(2)
    BIO:用时 10+1+1+2=14,第1个执行完用时10+1,等待第一个执行完处理第2个,用时1+2
    NIO:用时 1+2+7+1=11, 第二个数据先到,时间 1+2,此时第一个需要等时为10秒,还没到,还需等待7秒,时间为7+1
    3.两个线程处理两个请求
    第一个请求,等待请求数据(10),处理时间(1)
    第二个请求,等待请求数据(1),处理时间(2)
    BIO:用时 10+1+2=13,等待第1个请求10,交给工作线程一处理,此时同时接受第2个,等待1秒,处理时间2秒,此间线程一处理时间为一秒,在线程二结束之前就已经结束
    NIO:用时 1+2+7+1=11,第二个数据先到,时间 1+2,此时第一个还没到,还需等待7秒,时间为7+1
    如果两个请求顺序相反,则bio和nio一样,都是11秒
    由此可见由于阻塞等待机制的不同,导致效率不同,主要优化点为,不必排队等待,先到先处理,就有可能效率高一点。
    4.BIO如果想要处理并发请求,则必须使用多线程,一般后端会用线程池来支持
    NIO可以使用单线程,可以减少线程切换上下文的消耗。
    但是虽然单线程减少了线程切换的消耗,但是处理也变为线性的,也就是处理完一个请求,才能处理第二个。
    这时,有这么两个场景:

    1.后端是密集型的计算,没有大量的IO操作,比如读些文件、数据库等
    2.后端是有大量的IO操作
    当为第一种场景时:
    NIO单线程则比较有优势, 理由是虽然是单线程,但是由于线程的计算是并发计算,不是并行计算,说到底,计算压力还是在CPU上,一个线程计算,没有线程的多余消耗,显然比NIO多线程要高效。BIO则必为多线程,否则将阻塞到天荒地老,但多线程是并发,不是并行,主要还是依靠CPU的线性计算,另外还有处理大量的线程上下文。
    如果为第二种场景,多线程将有一定优势,多个线程把等待IO的时间能平均开。此时两者区别主要取决于以上分析的处理顺序了,显然NIO要更胜一筹。
     

  • 相关阅读:
    HPE脚本更新致京都大学77TB数据被删
    计算机毕业设计Java个性化穿搭推荐系统(源码+系统+mysql数据库+Lw文档)
    企业级自定义表单引擎解决方案(十二)--体验代码目录结构
    CentOS to 浪潮信息 KeyarchOS 迁移体验与优化建议
    [老文拾遗]如果我当上技术经理如何展开工作(三)
    面试题:Java反射和new效率对比,差距有多大?
    2022-10-27 工作记录--Swiper/React-解决swiper处理动态数据出现样式紊乱、抖动问题
    RabbitMQ学习总结
    基于J2EE的大型视频影音系统的设计与实现
    花998购买的拍摄技巧和7天起号培训文档,学了一周的总结。
  • 原文地址:https://blog.csdn.net/weixin_57128596/article/details/126202579