手把手带你写一个 MiniTomcat
郭屹
前 Sun Microsystems Java 研发工程师
1792 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 22 讲
开篇词 (1讲)
手把手带你写一个 MiniTomcat
15
15
1.0x
00:00/00:00
登录|注册

05|Server性能提升:设计多个Processor

你好,我是郭屹。今天我们继续手写 MiniTomcat。
学完前几节课的内容之后,现在我们已经做到接口满足 Servlet 规范,而且功能模块拆分成了 Connector 和 Processor 两部分,它们各司其职,一个负责网络连接,一个负责 Servlet 调用处理。
但现在这个 Server 的运行模式是,一个 Connector 服务于一个 Processor,而且每次创建 Processor 的时候都是重新实例化一个新的对象,Processor 还不支持多线程处理,所以我们在 HttpServer 性能方面还有提升的空间。
这节课,我们计划引入池的概念,增强 Processor 的复用性,同时将 Processor 异步化,支持一个 Connector 服务于多个 Processor。

项目结构

这节课,我们只针对原有的 HttpConnector 和 HttpProcessor 类进行改造,所以项目结构和 maven 引入依赖保持不变,还是延续下面的结构和配置。
MiniTomcat
├─ src
│ ├─ main
│ │ ├─ java
│ │ │ ├─ server
│ │ │ │ ├─ HttpConnector.java
│ │ │ │ ├─ HttpProcessor.java
│ │ │ │ ├─ HttpServer.java
│ │ │ │ ├─ Request.java
│ │ │ │ ├─ Response.java
│ │ │ │ ├─ ServletProcessor.java
│ │ │ │ ├─ StatisResourceProcessor.java
│ │ ├─ resources
│ ├─ test
│ │ ├─ java
│ │ │ ├─ test
│ │ │ │ ├─ HelloServlet.java
│ │ ├─ resources
├─ webroot
│ ├─ test
│ │ ├─ HelloServlet.class
│ ├─ hello.txt
├─ pom.xml
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了如何通过设计多个Processor来提升Server性能。作者指出了当前Server运行模式的不足之处,提出了引入池的概念,增强Processor的复用性,同时将Processor异步化,支持一个Connector服务于多个Processor的计划。在项目结构方面,作者针对原有的HttpConnector和HttpProcessor类进行了改造,使用ArrayDeque存放构造完毕的HttpProcessor对象,实现了多个Processor的池化管理。通过对HttpConnector类的改造,作者展示了如何初始化Processor池,并在接收到Socket时从池中获取Processor,处理完毕后再放回池中,从而实现了一次请求响应。文章通过具体的代码示例和解释,清晰地阐述了如何设计多个Processor来提升Server性能,为读者提供了一种优化Server性能的思路和方法。文章内容涉及了引入池化技术以及Processor多线程,从优化对象构造、持续复用的角度,以及并发的角度,使Connector能同时服务于多个Processor,减少了原来因等待Processor处理而产生的时间消耗,但是也需要仔细编写线程同步代码。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《手把手带你写一个 MiniTomcat》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(11)

  • 最新
  • 精选
  • 马以
    思考题:tomcat的线程池模型和JDK自带线程池模型在核心线程池用完后的实现方式上是不同的;JDK的线程池在达到核心线程池数量后,后续的请求会进入到等待队列(微观上看属于阻塞),因为tomcat作为servlet服务请求,本质上只能并发处理有限个(核心线程数)的并发数,这显然是不合理的;所以tomcat的线程池模型是达到核心线程池后会继续启动新线程处理请求,直到达到最大线程数;

    作者回复: 赞。

    2024-01-04归属地:广东
    3
  • 像少年样飞驰
    这里面线程同步机制需要这样写么? 直接参考线程池的设计,用一个阻塞队列是不是就可以了? 本质上还是一个生产和消费的模型吧

    作者回复: 也是可以的,但是jdk线程池不是很适合,所以Tomcat采用了这个方式(有人也有类似问题,见别的解答)。对我个人来说,是忠于Tomcat的原始实现,以后学习者读到Tomcat这一段源代码的时候不会觉得难以理解。

    2023-12-24归属地:上海
    1
  • so long
    jdk线程池,在并发数超过核心线程数后,会先将请求任务添加到队列中,而不是创建新的线程处理请求任务,所以会存在一定的延迟

    作者回复: 是的。jdk线程池用的普通队列,不适合io型多任务处理。

    2023-12-18归属地:浙江
    1
  • 无心
    avaliable的 ture 和 false 语意是不是反了

    作者回复: 没反。就是一个闸门,控制两边。看你从哪面去看这个问题。

    2024-03-09归属地:广东
  • Geek_b7bd01
    为什么connector调用processor.assign()方法,这样不会导致connector线程进行等待吗?为何不直接将任务丢给processor。

    作者回复: 直接丢给processor不行,生产者消费者之间要同步。

    2024-01-31归属地:广东
    2
  • Geek_b7bd01
    这样connector不是必须得等待processor执行完成之后才能继续往下走吗,不影响效率吗?

    作者回复: 并不会等processor执行完,那就成了串行了。互锁机制解决生产者消费者同步问题。

    2024-01-31归属地:广东
  • ctt
    HttpProcessor initprocessor = new HttpProcessor(); processors.push(initprocessor); curProcessors++; return ((HttpProcessor) processors.pop()); 请问这段代码为什么push完就立即pop了呢

    作者回复: 因为本质上是需要拿出一个processor.只是没有的时候会临时新建一个。

    2023-12-24归属地:广东
  • C.
    Tomcat 为什么用一个简单的 queue 来实现多线程而不是用 JDK 自带的线程池? 1.自定义可以更好地控制,还有后期的优化 2.历史原因,可能当时内置线程池功能没那么完善 现在,应该也支持使用JDK自带的线程池 交个作业:https://github.com/caozhenyuan/mini-tomcat

    作者回复: 你的理解原则上是正确的。具体一点,jdk线程池标准模式当核心线程数超过后直接进队列,不是新建线程,这个方式不适合io型多任务。

    2023-12-19归属地:江苏
  • peter
    请教老师几个问题: Q1:用notifyAll唤醒所有线程,不对吧。 假如有5个线程,connector同时启动这5个线程,5个线程处于wait状态。假设此时来了一个连接请求,由其中的一个线程A处理,那么,connector应该只唤醒这线程A吧。用notifyall会唤醒全部5个线程,难道5个线程处理同一个请求吗? Q2:recycle方法有多线程问题吗? 假设有5个线程在处理5个请求,这5个线程都会调用recycle方法,此时会存在并发问题吗? Q3:线程池的大小一般为多大? 有经验公式吗?

    作者回复: 都是好问题。notifyall会唤醒全部,但是代码中一旦标志不符合就会继续等待,不会出现几个同时处理,你仔细跟代码。recycle我也认为会出现并发问题,你这个观察很好,这段代码取自Tomcat4源代码,应该是有问题。 线程池大小,很复杂,大师们有个公式,依赖于核还有计算时间等待时间,但是实际系统复杂,很不好估算计算时间等待时间,并且还有别的进程共用CPU,所以呢,不好回答,我一般拍脑袋就是讲线程池大小设置为核的数量。

    2023-12-18归属地:北京
    4
  • HH🐷🐠
    🌝🌝自带JDK线程池初始化指定线程数, 共用这些线程, 可能这次在A线程执行, 下次在B线程执行,上下文切来切去造成性能不必要的开销,在网络中这点开销算是很大了。 只能想到这个点,不知道是否正确

    作者回复: 简单来讲,jdk线程池使用普通队列,当并发任务数超过了核心大小后,直接进队列等待,这种方式比较适合CPU型多任务。但是servlet的应用场景是io型的,所以Tomcat要自己实现。我们也可以比较简单地重写jdk队列的offer方法,数量没有达到最大线程数就返回false,让线程池新建线程。

    2023-12-18归属地:广东
收起评论
显示
设置
留言
11
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部