人的痛苦会把自己折磨到多深呢?
You cannot swim for new horizons until you have courage to lose sight of the shore.
除非有勇气离开岸边,否则你永远游不到彼岸。
IO 是主存和外部设备(硬盘、终端和网络等)拷贝数据的过程。IO是操作系统的底层功能实现,底层通过I/O指令进行完成。
以下是5种类Unix下可用的I/O模型
阻塞式I/O:Blocking IO
非阻塞式I/O:nonblocking IO
I/O 复用(Select,poll epoll):IO multiplexing
信号驱动式I/O(SIGIO):signal driven IO
异步 I/O(posix 的 aio 系列函数):asynchromous IO
在 Linux 中,默认情况下所有的 socket 都是 Blocking,一个典型的读操作流程大概是这样:
通常涉及等待数据从网络到达。当所有等待数据到达时,它被复制到内核中的某个缓冲区
把数据从内核缓冲区复制到应用程序缓冲区
当用户进程调用了 recvfrom 这个系统调用, kernel 就开始了 IO 的第一个阶段:准备数据。对于 network IO 来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的 UDP 包)。这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当 kernel 一直等到数据准备好了,它就会将数据从kernel 中拷贝到用户内存,然后kernel返回结果,用户进程才解除 block 的状态,重新运行起来。
所以,Blocking IO 的特点就是在IO执行的两个阶段都被 block了
Linux 下,可以用过设置 socket 使其变为 non-blocking。当对一个 non-blocking socket 执行操作时,流程如下:
从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。 从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次 发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,用户进程第一个阶段不是阻塞的,需要不断的主动询问kernel数据好了没有;第二个阶段依然总是阻塞的。
select、epoll 的好处就在于单个 process 就可以同时处理多个网络链接的 IO。
IO 复用和同步阻塞本质一样,不过利用了新的 Select 系统调用,由内核来负责本来是请求进程该做的轮训操作,看似不非阻塞IO还多了哥系统调用开销,不过因为支持多路IO才算提高了效率
也就是一个可以监听多个。
它的基本原理就是 select、epoll 这个 function会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。它的流程如下图:
当用户线程调用select,那么整个进程会被阻塞,而同时,kernel会监视所有select负责的 socket =,当任何一个 socket 中的数据准备好了,select 就会返回,这个时候用户进程会调用 read 操作,将数据 kernel 拷贝到用户进程。
首先开启套接字的信号驱动式IO功能,并且通过sigaction(信号处理程序) 系统调用安装一个信号处理函数 ,该函数调用将立即返回,当前进程没有被阻塞 ,继续工作;当数据报准备好的时候,内核为该进程产生SIGIO 的信号,随后既可以在信号处理函数中调用recvfrom 读取数据报,并且通知主循环数据已经准备好等待处理;也可以直接通知主循环让它读取数据报;(其实就是一个待读取的通知和待处理的通知),基本不会用到。
多线程和多进程的模型虽然解决了并发的问题,但是系统不能无限的增加线程,由于系统的切换线程的开销恒大,所以,一旦线程数量过多,CPU的时间就花在线程的切换上,正真运行代码的时间就会减少,结果导致性能严重下降
由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一个问题的一种方法。
另一种解决IO问题的方法是异步IO,当代码需要执行一个耗时的IO操作时,他只发出IO指令,并不等待IO结果然后就去执行其他代码,一段时间后,当IO返回结果是,在通知CPU进行处理我们调用aio_read函数,给内核传递描述符,缓冲区指针,缓冲区大小,和文件偏移量,并且告诉内核当整个操作完成时如何通知我们,该函数调用后,立即返回,不会被阻塞
另一方面:从kernel的角度,当他收到一个aio_read之后,首先它立即返回,所以不会对用户进程产生block,然后kernel会等待数据准备完成,然后将数据拷贝到用户内存(copy由内核完成),当着一切完成后,kernel会给用户进程发送一个singal或者执行下一个基于线程回调函数来完成此次IO处理过程,告诉他read操作完成
岁月应是静好。近来,心神总是不宁,想必是被尘世间的诱惑所困。
我想,要是远方的祥夫先生来到我这“梅知堂”,两人清茶一杯,盘腿而坐,或看他画梅,纸上的梅花仿佛兀自开在飞雪里,如是,那该是一幅怎样的墨梅图?
昨晚,梦里看到那个戴着小黑圆眼镜的祥夫背着黄色的大包正往江南赶路。是又到了梅花开的日子了吗?
1. 客户端发送请求到前端控制器 DispatcherServlet 2. DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器,请求获取 handler 3. 处理器映射器根据 url 找到具体的处理器,生成处理器对象以及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet 4. DispatcherServlet 调用 HandlerAdapter 处理器适配器 5. HandlerAdapter 经过适配器调用 具体处理器(Handler,也叫后端控制器) 6. Handler 执行完成返回 ModelAndView 7. HandlerAdaper 将 Handler 执行结果 ModelAndView 返回给 DispatcherServlet 8. DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器 进行解析 9. ViewResolver 解析后返回具体 View 10. DispatcherServlet 对 view 进行 渲染视图(即将模型数据填充至视图中) 11. DispatcherServlet 响应用户
ArrayList | LinkedList | Vector | |
线程是否安全 | 不保证 | 不保证 | 保证线程安全,但是底层大量使用了synchronized关键字,效率不是很高 |
底层数据结构 | 数组,查询效率高,多用于查询较多的场合 | LinkedList 底层是双向链表,插入和删除非常方便,适用于插入较多的场合 | 数组,查询效率高,多用于查询较多的场合 |
默认大小与扩容 | JDK 1.7 之前默认大小是 10 JDK 1.7 之后是0 每次按照1.5倍扩容 | 底层是链表结构,是不连续的存储空间,没有默认大小的说法 | Vector扩容是2倍 |
@RestController
注解,在 @Controller
基础上,增加了 @ResponseBody
注解,更加适合目前前后端分离的架构下,提供 Restful API ,返回例如 JSON 数据格式。当然,返回什么样的数据格式,根据客户端的 "ACCEPT"
请求头来决定。