在关于多线程的文章中,了解过同步和异步机制,这是一个相对的术语,适用于所有类型的计算,而不仅仅是IO操作。通常异步指得是某些操作发生在另一个线程中(相对于发起请求IO计算的线程)
而阻塞和非阻塞是一种调用时的状态。
阻塞式调用则是:发起任务后,等待收到任务结果才能继续执行。
非阻塞式调用:发起任务后,在等待任务结果时,还能继续执行其他任务。
通常我们会把同步异步、阻塞非阻塞混为一谈,其实他们之间没有必然的联系,按照排列组合,也是有四种情况。
不过也有说法只有存在三种,即同步阻塞、同步非阻塞、异步
在广义上,”阻塞“和”非阻塞“可以粗略地来表示”同步“和”异步“,从这个意义来说,没有阻塞异步和非阻塞异步,这种说法是多余的,因为异步本身就是为了非阻塞。
但在狭义的观点上来看,阻塞和非阻塞指的是不同的内核IO接口,我们通常编写的代码的IO操作,最终都需要内核通过阻塞或非阻塞接口执行。
在之前文章中我们也了解过Socket,翻译为套接字。
Socket是基于Unix,一切皆文件的思想。它是TCP/IP协议的抽象,是操作系统对外开放的接口。
Socket是网络通信中端点,网络通信的两端通过socket来进行数据交互。
出现于JDK1.4之前,基于阻塞IO实现
通过IO流来进行数据交互,输入流和输出流都是单向的
每有一个客户端来连接服务器,服务器都会为其生成一个线程响应客户端请求。
缺点:
基于阻塞式IO,服务器线程会一直阻塞,等待客户端的请求。每一个请求对应一个线程,严重消耗服务器资源。当线程数量过多,会导致服务器性能瓶颈。
JDK1.4之后出现,适应于高并发场景
NIO提供了三个核心类:
Channel(通道)、Buffer(缓冲区)、Selector(多路复用器)
Channel取代了BIO中Stream的作用,在BIO中的数据交互是通过IOStream来交互的。
Stream是有方向的,InputStream和OutPutStream,且Stream是阻塞式的。
而Channel是双向性IO,提供了阻塞和非阻塞式调用。
对Channel的操作是唯一性,只能通过Buffer操作Channel。
Channel的实现类:
Buffer类的唯一作用就是读写Channel中的数据,其本质就是一块内存区域。针对Channel的操作,都是离不开对Buffer的操作。
Buffer的关键属性:
Selector用来监听多个Channel是否处于可操作状态。
Selector监听Channel,需要Channel注册在Selector上,注册一个Channel后,会得到一个SelectionKey对象(相当于Channel的id),对于每个注册的Channel都有与之对应的独特的SelectionKey。
通过SelectionKey可以获取到四种就绪状态常量:
NIO的工作流程相较于BIO要复杂得多:
Selector负责阻塞式监听是否有Channel注册的事件发生,有就调用相对应的处理器进行处理。
Handle方法和Select方法都在同一个线程上。
使用单线程Selector来监听事件,解决了线程的创建和销毁,上下文切换等,节省了服务器资源。
缺点:
类库的API的繁琐,决定了使用NIO的难度和工作量都要远高于BIO
AIO–Asynchronous IO(异步IO),是从JDK1.7之后引用,也叫NIO.2。
相较于前两种同步IO,AIO采用的是异步IO机制,当后台处理完后,会通知线程进行后续操作
目前AIO的应用并不广泛,主流的还是NIO。