【大厂求职必看】太强了!Tomcat线程模型全网最清晰讲解!(下)

简介: 【大厂求职必看】太强了!Tomcat线程模型全网最清晰讲解!

初始化

protected void initServerSocket() throws Exception {
    if (!getUseInheritedChannel()) {
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
        serverSock.socket().bind(addr,getAcceptCount());
    } else {
        // Retrieve the channel provided by the OS
        Channel ic = System.inheritedChannel();
        if (ic instanceof ServerSocketChannel) {
            serverSock = (ServerSocketChannel) ic;
        }
        if (serverSock == null) {
            throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
        }
    }
    // 阻塞模式
    serverSock.configureBlocking(true); //mimic APR behavior
}

bind方法的 getAcceptCount() 参数表示os的等待队列长度。当应用层的连接数到达最大值时,os可以继续接收连接,os能继续接收的最大连接数就是这个队列长度,可以通过acceptCount参数配置,默认是100


image.png



ServerSocketChannel通过accept()接受新的连接,accept()方法返回获得SocketChannel对象,然后将SocketChannel对象封装在一个PollerEvent对象中,并将PollerEvent对象压入Poller的Queue里。

这是个典型的“生产者-消费者”模式,Acceptor与Poller线程之间通过Queue通信。


Poller

本质是一个Selector,也跑在单独线程里。

Poller在内部维护一个Channel数组,它在一个死循环里不断检测Channel的数据就绪状态,一旦有Channel可读,就生成一个SocketProcessor任务对象扔给Executor去处理。


内核空间的接收连接是对每个连接都产生一个channel,该channel就是Acceptor里accept方法得到的scoketChannel,后面的Poller在用selector#select监听内核是否准备就绪,才知道监听内核哪个channel。

维护了一个 Queue:


image.png



SynchronizedQueue的方法比如offer、poll、size和clear都使用synchronized修饰,即同一时刻只有一个Acceptor线程读写Queue。

同时有多个Poller线程在运行,每个Poller线程都有自己的Queue。

每个Poller线程可能同时被多个Acceptor线程调用来注册PollerEvent。

Poller的个数可以通过pollers参数配置。



职责


Poller不断的通过内部的Selector对象向内核查询Channel状态,一旦可读就生成任务类SocketProcessor交给Executor处理


image.png



  • Poller循环遍历检查自己所管理的SocketChannel是否已超时。若超时就关闭该SocketChannel


SocketProcessor


Poller会创建SocketProcessor任务类交给线程池处理,而SocketProcessor实现了Runnable接口,用来定义Executor中线程所执行的任务,主要就是调用Http11Processor组件处理请求:Http11Processor读取Channel的数据来生成ServletRequest对象。


Http11Processor并非直接读取Channel。因为Tomcat支持同步非阻塞I/O、异步I/O模型,在Java API中,对应Channel类不同,比如有AsynchronousSocketChannel和SocketChannel,为了对Http11Processor屏蔽这些差异,Tomcat设计了一个包装类叫作SocketWrapper,Http11Processor只调用SocketWrapper的方法去读写数据。



Executor

线程池,负责运行SocketProcessor任务类,SocketProcessor的run方法会调用Http11Processor来读取和解析请求数据。我们知道,Http11Processor是应用层协议的封装,它会调用容器获得响应,再把响应通过Channel写出。


Tomcat定制的线程池,它负责创建真正干活的工作线程。就是执行SocketProcessor#run,即解析请求并通过容器来处理请求,最终调用Servlet。


Tomcat的高并发设计



高并发就是能快速地处理大量请求,需合理设计线程模型让CPU忙起来,尽量不要让线程阻塞,因为一阻塞,CPU就闲了。

有多少任务,就用相应规模线程数去处理。

比如NioEndpoint要完成三件事情:接收连接、检测I/O事件和处理请求,关键就是把这三件事情分别定制线程数处理:



  • 专门的线程组去跑Acceptor,并且Acceptor的个数可以配置
  • 专门的线程组去跑Poller,Poller的个数也可以配置
  • 具体任务的执行也由专门的线程池来处理,也可以配置线程池的大小

总结


I/O模型是为了解决内存和外部设备速度差异。


  • 所谓阻塞或非阻塞是指应用程序在发起I/O操作时,是立即返回还是等待
  • 同步和异步,是指应用程序在与内核通信时,数据从内核空间到应用空间的拷贝,是由内核主动发起还是由应用程序来触发。


Tomcat#Endpoint组件的主要工作就是处理I/O,而NioEndpoint利用Java NIO API实现了多路复用I/O模型。

读写数据的线程自己不会阻塞在I/O等待上,而是把这个工作交给Selector。


当客户端发起一个HTTP请求时,首先由Acceptor#run中的

socket = endpoint.serverSocketAccept();

接收连接,然后传递给名称为Poller的线程去侦测I/O事件,Poller线程会一直select,选出内核将数据从网卡拷贝到内核空间的 channel(也就是内核已经准备好数据)然后交给名称为Catalina-exec的线程去处理,这个过程也包括内核将数据从内核空间拷贝到用户空间这么一个过程,所以对于exec线程是阻塞的,此时用户空间(也就是exec线程)就接收到了数据,可以解析然后做业务处理了。



参考

目录
相关文章
|
27天前
|
存储 缓存 关系型数据库
MySQL底层概述—3.InnoDB线程模型
InnoDB存储引擎采用多线程模型,包含多个后台线程以处理不同任务。主要线程包括:IO Thread负责读写数据页和日志;Purge Thread回收已提交事务的undo日志;Page Cleaner Thread刷新脏页并清理redo日志;Master Thread调度其他线程,定时刷新脏页、回收undo日志、写入redo日志和合并写缓冲。各线程协同工作,确保数据一致性和高效性能。
MySQL底层概述—3.InnoDB线程模型
|
7月前
|
编解码 网络协议 API
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
|
5月前
|
并行计算 JavaScript 前端开发
单线程模型
【10月更文挑战第15天】
|
5月前
|
Java 应用服务中间件
面对海量网络请求,Tomcat线程池如何进行扩展?
【10月更文挑战第4天】本文详细探讨了Tomcat线程池相较于标准Java实用工具包(JUC)线程池的关键改进。首先,Tomcat线程池在启动时即预先创建全部核心线程,以应对启动初期的高并发请求。其次,通过重写阻塞队列的入队逻辑,Tomcat能够在任务数超过当前线程数但未达最大线程数时,及时创建非核心线程,而非等到队列满才行动。此外,Tomcat还引入了在拒绝策略触发后重新尝试入队的机制,以提高吞吐量。这些优化使得Tomcat线程池更适应IO密集型任务,有效提升了性能。
面对海量网络请求,Tomcat线程池如何进行扩展?
|
5月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
50 1
|
6月前
|
消息中间件 存储 NoSQL
剖析 Redis List 消息队列的三种消费线程模型
Redis 列表(List)是一种简单的字符串列表,它的底层实现是一个双向链表。 生产环境,很多公司都将 Redis 列表应用于轻量级消息队列 。这篇文章,我们聊聊如何使用 List 命令实现消息队列的功能以及剖析消费者线程模型 。
144 20
剖析 Redis List 消息队列的三种消费线程模型
|
5月前
|
Dubbo Java 应用服务中间件
剖析Tomcat线程池与JDK线程池的区别和联系!
剖析Tomcat线程池与JDK线程池的区别和联系!
242 0
剖析Tomcat线程池与JDK线程池的区别和联系!
|
5月前
|
NoSQL Redis 数据库
Redis单线程模型 redis 为什么是单线程?为什么 redis 单线程效率还能那么高,速度还能特别快
本文解释了Redis为什么采用单线程模型,以及为什么Redis单线程模型的效率和速度依然可以非常高,主要原因包括Redis操作主要访问内存、核心操作简单、单线程避免了线程竞争开销,以及使用了IO多路复用机制epoll。
97 0
Redis单线程模型 redis 为什么是单线程?为什么 redis 单线程效率还能那么高,速度还能特别快
|
5月前
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。
|
5月前
|
消息中间件 NoSQL 关系型数据库
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
74 0