1、IO多路复用:select、poll、epoll的区别(非常重要,几乎必问,回答得越底层越好,要会使用)
目前的常用的IO复用模型有三种:select,poll,epoll。
select模型:
说的通俗一点就是各个客户端连接的文件描述符也就是套接字,都被放到了一个集合中,调用select函数之后会一直监视这些文件描述符中有哪些可读,如果有可读的描述符那么我们的工作进程就去读取资源。PHP 中有内置的函数来完成 select 系统调用。
poll模型:
poll 和 select 的实现非常类似,本质上的区别就是存放 fd 集合的数据结构不一样。select 在一个进程内可以维持最多 1024 个连接,poll 在此基础上做了加强,可以维持任意数量的连接。
但 select 和 poll 方式有一个很大的问题就是,我们不难看出来 select 是通过轮训的方式来查找是否可读或者可写,打个比方,如果同时有100万个连接都没有断开,而只有一个客户端发送了数据,所以这里它还是需要循环这么多次,造成资源浪费。
所以后来出现了 epoll系统调用。
epoll模型:
epoll 是 select 和 poll 的增强版,epoll 同 poll 一样,文件描述符数量无限制。
epoll是基于内核的反射机制,在有活跃的 socket 时,系统会调用我们提前设置的回调函数。而 poll 和 select 都是遍历。
2、手撕一个最简单的server端服务器(socket、bind、listen、accept这四个API一定要非常熟练)
3、线程池
又以上介绍我们可以看出,在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
那么,我们应该如何创建一个线程池那?Java中已经提供了创建线程池的一个类:Executor
而我们创建时,一般使用它的子类:ThreadPoolExecutor.
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
这是其中最重要的一个构造方法,这个方法决定了创建出来的线程池的各种属性,下面依靠一张图来更好的理解线程池和这几个参数:
又图中,我们可以看出,线程池中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收,maximumPoolSize就是线程池中可以容纳的最大线程的数量,而keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间,而util,就是计算这个时间的一个单位,workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory,就是创建线程的线程工厂,最后一个handler,是一种拒绝策略,我们可以在任务满了知乎,拒绝执行某些任务。
线程池的执行流程又是怎样的呢?
有图我们可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。
handler的拒绝策略:
有四种:第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
第二种DisCardPolicy:不执行新任务,也不抛出异常
第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
第四种CallerRunsPolicy:直接调用execute来执行当前任务
五,四种常见的线程池:
CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
4、基于事件驱动的reactor模式
reactor模式是事件驱动架构的一种实现技术。简单来说,它使用一个单线程的事件循环阻塞在产生资源的事件上,并且将它们派发给相应的处理器和回调函方法上。
没有必要阻塞在I/O上,只要事件的处理器和回调已经注册好了来处理它们。事件指向一些实例比如一个新进来的准备好读或者准备好写的连接,等等。在多核环境中,那些处理器或者回调可能会使用一个线程池。
这种模式将模块化的应用水平的代码从可复用的reactor实现中解耦了出来。
Reactor 模式的意图
Reactor架构模式允许将事件驱动应用于多路分配和派发来自于一个或者多个客户端发送到一个应用的服务请求。
reactor 将不断的寻找事件,一旦事件得到了触发将通知相应的事件处理器去处理。
The Reactor Pattern is a design pattern for synchronous demultiplexing and order of events as they arrive.
Reactor 模式是一种设计模式,用来处理同步多路分配和事件按照顺序到达(翻译不准。。。)
它接收来自于多个并发客户端的消息,请求和连接,使用事件处理器顺序的处理这些请求。Reactor设计模式的目的是避免为每一个消息,请求,连接创建一个线程的常见问题。然后它从一批处理器中获取事件,将这些事件有序的派发给相应的事件处理器。
简单来说,服务端需要处理超过1万个客户端并发连接,使用tomcat,jboss里的线程没法处理相应规模的连接。
所以,应用程序使用reactor 只需要一个线程处理同时产生的事件。
基本上,标准的Reactor允许一个领导应用处理同时产生的事件,同时保持单个线程的简易性。
多路分配器是一种回路,有一个输入多个输出。它是一个回路当你想发送信号给多个设备中的一个设备。
这种描述听起来类似于解码器的描述,解码器是用于在多个设备中选择,而一个多路分配器用于在多个设备中发送一个信号。
Reactor允许使用一个独立的线程处理多个阻塞地等待被处理的任务。Reactor也管理一个事件处理器的集合。当被唤起处理任务事,它连接可用的处理器并且使它活跃。
事件循环:
1)找到所有活跃的和解锁的处理器 或者将这事情委托给一个分发器实现。
2)有序地执行每个处理器直到完成为止,或者到达他们被阻塞的一个点。已经完成的处理器是无效的,允许继续事件循环。
3)从步骤1开始循环
5、边沿触发与水平触发的区别
边缘触发(edge-triggered)
简称:ET,它只支持非阻塞socket。你可以设定一个值,当到达这个值时才会触发。它只通知一次。如果你不对其事件进行处理,它将会将其丢弃。
水平触发(level-triggered)
简称:LT,它支持阻塞和非阻塞两种模式,它是一有事件发生触发,如果你不其进行处理,它不会将事件丢弃,它将会一直提示你。
6、非阻塞IO与阻塞IO区别
1、典型应用:阻塞socket、Java BIO;
2、特点:
不适用并发量大的应用:因为一个请求IO会阻塞进程,所以,得为每请求分配一个处理进程(线程)以及时响应,系统开销大。
进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞;进程发起IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回进程。
对于上面的阻塞IO模型来说,内核数据没准备好需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞。
1、典型应用:socket是非阻塞的方式(设置为NONBLOCK)
2、特点:
多个的进程的IO可以注册到一个复用器(select)上,然后用一个进程调用该select, select会监听所有注册进来的IO;
如果select没有监听的IO在内核缓冲区都没有可读数据,select调用进程会被阻塞;而当任一IO在内核缓冲区中有可数据时,select调用就会返回;
而后select调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据。
可以看到,多个进程注册IO后,只有另一个select调用进程被阻塞。
1、典型应用:select、poll、epoll三种方案,nginx都可以选择使用这三个方案;Java NIO;
2、特点:
3、select、poll、epoll