怎么做外汇返佣的网站杭州seo推广公司
一、什么是 I/O?
I/O 描述了计算机系统与外部设备(磁盘)之间通信的过程。
为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为 用户空间(User space) 和 内核空间(Kernel space ) 。用户进程(应用程序)想要执行 IO 操作的话,必须通过 系统调用 来间接访问内核空间。
当应用程序发起 I/O 调用后,会经历两个步骤:
- 内核等待 I/O 设备准备好数据——阶段①
- 内核将数据从内核空间拷贝到用户空间——阶段②
二、同步与异步、阻塞与非阻塞
阻塞与非阻塞是针对 线程 来说的;同步与异步是针对 整个I/O操作 来说的
同步阻塞IO(BIO):应用程序发起请求后,需等待阶段①和②都完成,在整个IO期间不能做别的,必须等待整个IO操作完成才可进行下个任务。
同步非阻塞IO:应用程序发起请求后,在阶段①不断轮询内核数据是否准备好,在阶段②需阻塞等待,在整个IO期间不能做别的,必须等待整个IO操作完成才可进行下个任务。
异步阻塞IO:应用程序发起请求后,立刻返回,在等待阶段①和②期间也不做别的(因为阻塞挂起当前线程),就等着内核通知。内核完成①和②之后,发起回调,应用程序可以直接处理数据。
异步非阻塞IO(AIO):应用程序发起请求后,立刻返回,在等待阶段①和②期间做别的事。内核完成①和②之后,发起回调,应用程序可以直接处理数据。
简述JAVA同步、异步、阻塞和非阻塞之间的区别_java_脚本之家 (jb51.net)
什么是阻塞和非阻塞?什么是同步和异步?什么是BIO、NIO、AIO? - 沙滩de流沙 - 博客园 (cnblogs.com)
三、 I/O 多路复用模型 (NIO)
同步非阻塞IO的应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。
IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。
IO 多路复用模型中,线程首先发起 select 调用(阶段①),询问内核数据是否准备就绪(一个线程管理多个客户端连接,也就是在这期间询问每个连接),等内核把数据准备好了(某一个连接),用户线程再发起 read 调用(阶段②)。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。
四、深入理解
同步与异步(是谁通知消息)
同步: 发起一个调用后,被调用者未处理完请求之前,调用不返回。需要反复询问数据是否就绪。
异步: 发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。
同步和异步的区别最大在于——异步是被调用者来通知调用者处理结果。同步需要调用者自己反复询问处理结果。
阻塞和非阻塞(线程等待消息通知时的可不可以做别的事)
阻塞: 发起一个请求,调用者一直等待请求结果返回,无法从事其他任务,只有当条件就绪才能继续。
非阻塞: 发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。
同步非阻塞 和 异步非阻塞 区别
- 同步阻塞 是自己发起之后,就一直等待处理结果。
- 同步非阻塞 是自己等待消息通知,需自己反复询问处理结果。
- 异步非阻塞 是别人通过事件或回调机制来通知自己,自己不需要询问。
BIO(Socket 网络编程)
- 同步阻塞I/O
- 一请求一线程
- 面向流(Stream)。数据直接读写到 Stream 对象中。
- 不适合高并发场景
在 Java 虚拟机中,线程是宝贵的资源,线程的创建和销毁成本很高,除此之外,线程的切换成本也是很高的。
痛点
- 处理多个客户端请求,就必须使用多线程。如果这个连接不做任何事情的话就会造成不必要的线程开销。线程开销大。线程之间的切换也会浪费资源开销。
- 如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务。
优化(伪异步IO)
- 可以通过 线程池机制 改善。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
- 避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层任然是同步阻塞的BIO模型,因此无法从根本上解决问题。
NIO(SocketChannel 网络编程)
- I/O 多路复用模型
- 多通道一线程
- 面向缓冲区。所有数据读写到缓冲区。
- 三大核心组件:
- Buffer(缓冲区)。在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
- Channel(通道)。NIO 通过Channel(通道) 进行读写。通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。
- Selector(选择器)。NIO的选择器用于使用单个线程管理多个通道。只有在通道里真正有读写事件发生时(事件驱动),才会交给线程读写(Selector会一直询问每个通道有没有读写事件,这个过程是同步的),因此,它只需要较少的线程来处理这些通道。不必为每一个连接都创建一个线程,也不必去维护多个线程。避免了多个线程之间的上下文切换,导致资源的浪费。(只有网络IO才会使用选择器,文件IO是不需要使用的)
- 适合高并发场景
痛点
- NIO的类库和API繁杂,学习成本高。
- 需要熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模式,你必须对多线程和网络编程非常熟悉,才能写出高质量的NIO程序。
-
JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%
当我们调用socket.read()、socket.write()这类阻塞函数的时候,这类函数不能立即返回,也无法中断,需要等待socket可读或者可写,才会返回,因此一个线程只能处理一个请求。在这等待的过程中,cpu并不干活,(即阻塞住了),那么cpu的资源就没有很好地利用起来。因此对于这种情况,我们使用多线程来提高cpu资源的利用率:在等待的这段时间,就可以切换到别的线程去处理事件,直到socket可读或可写了,通过中断信号通知cpu,再切换回来继续处理数据。例如线程A正在等待socket可读,而线程B已经就绪了,那么就可以先切换到线程B去处理。虽然上下文切换也会花一些时间,但是远比阻塞在线程A这里空等要好。当然计算机内部实际的情况比这复杂得多。
而NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:如果一个连接不能读写(socket.read()返回0或者socket.write()返回0),我们可以把这件事记下来。因此只需要一个Selector不断地轮询这些事件,一旦有就绪的时间,处理即可。不需要多线程。
AIO
- 异步非阻塞I/O
- 异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回(发起请求后,线程直接返回干别的事,这个过程是异步的,只要等待操作完成后,通知该线程),不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
痛点
- 复杂性:虽然AIO的编程模型相对简单,但是由于其非阻塞的特性,编程复杂性可能会增加。例如,需要处理操作完成的通知,以及可能的并发问题。
- 资源消耗:AIO可能会消耗更多的系统资源。因为每个操作都需要创建一个回调函数,如果并发连接数非常大,可能会消耗大量的系统资源。
- 可移植性:AIO在某些平台上可能不可用或者性能不佳。因此,如果需要跨平台的可移植性,可能需要考虑使用其他I/O模型。
BIO适合连接数目较少且固定的架构。
NIO适合连接数目多,但是并发读写操作相对较少的场景。
AIO则适合连接数目多,且并发读写操作也多的场景。
五、思考题
看看你现在能回答这些问题了吗?
- 简单说下 BIO、NIO 和 AIO?具体使用、区别及原理?
- BIO,NIO,AIO的痛点,怎么优化?
- 为什么BIO比NIO性能差?简单讲讲区别?
- 假设有100个连接,采用NIO的方式要服务端要分配几个线程,采用BIO的方式呢?
阿里毕玄-测试Java编程能力-我的回答(一)_java bio建立100个连接-CSDN博客
- 为啥要用异步IO不用多线程,不是一样可以加速吗?
- NIO的设计架构?JDK中NIO有哪些重要组件?
- 同步、异步调用方式的具体实现