第七章:实现多线程支持

在本章中,我们将为 MiniTomcat 引入多线程支持,旨在显著提升服务器的并发处理能力。通过引入线程池(Thread Pool)机制,服务器能够同时处理多个客户端请求,避免单线程模型下的请求阻塞问题。这将使服务器在面对高并发场景时更加高效,更好地支持多用户同时访问。

7.1 功能目标

本章的核心改造目标如下:

  • 多线程支持:利用线程池管理线程资源,为每个客户端请求分配独立的线程进行处理。
  • 线程池机制:避免为每个请求频繁创建和销毁线程,通过复用线程降低系统开销,提高执行效率。
  • 并发处理:支持多个客户端同时访问不同的 Servlet,确保请求之间互不干扰,实现真正的并发处理。

7.2 代码结构

本次修改将引入线程池机制,主要通过 ExecutorService 来管理工作线程。项目代码结构更新如下:

MiniTomcat
├─ src
│ ├─ main
│ │ ├─ java
│ │ │ ├─ com.daicy.minitomcat
│ │ │ │ ├─ servlet
│ │ │ │ │ ├─ CustomServletOutputStream.java // 自定义的 Servlet 输出流类
│ │ │ │ │ ├─ CustomHttpSession.java         // 自定义的 HttpSession
│ │ │ │ │ ├─ HttpServletRequestImpl.java    // HTTP 请求的实现类
│ │ │ │ │ ├─ HttpServletResponseImpl.java   // HTTP 响应的实现类
│ │ │ │ │ ├─ ServletConfigImpl.java         // Servlet 配置的实现类
│ │ │ │ │ ├─ ServletContextImpl.java        // Servlet 上下文的实现类
│ │ │ │ ├─ CounterServlet.java              // Session 功能 Servlet 示例类
│ │ │ │ ├─ HelloServlet.java                // Servlet 示例类
│ │ │ │ ├─ HttpConnector.java               // 连接器类
│ │ │ │ ├─ HttpProcessor.java               // 请求处理器
│ │ │ │ ├─ HttpServer.java                  // 主服务器类
│ │ │ │ ├─ HttpRequestParser.java           // HttpRequest 信息解析类
│ │ │ │ ├─ ServletLoader.java               // Servlet 加载器
│ │ │ │ ├─ ServletProcessor.java            // Servlet 处理器
│ │ │ │ ├─ StaticResourceProcessor.java     // 静态资源处理器
│ │ │ │ ├─ SessionManager.java              // Session 管理器
│ │ │ │ ├─ ThreadPool.java                  // 线程池管理
│ │ │ │ ├─ WebXmlServletContainer.java      // Servlet 容器相关类
│ │ ├─ resources
│ │ │ ├─ webroot
│ │ │ │ ├─ index.html
│ │ │ ├─ web.xml
│ ├─ test
├─ pom.xml

7.3 代码实现

7.3.1 创建 ThreadPool

我们将基于 Java 并发包中的 ThreadPoolExecutor 来实现线程池管理。ThreadPool 类负责封装线程池的生命周期,并提供提交任务的接口。

package com.daicy.minitomcat;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPool {
    private ExecutorService executor;

    public ThreadPool(int poolSize) {
        // 创建固定大小的线程池
        executor = Executors.newFixedThreadPool(poolSize);
    }

    public void submitTask(Runnable task) {
        executor.submit(task);
    }

    public void shutdown() {
        if (executor != null) {
            executor.shutdown();
        }
    }

    public boolean isShutdown() {
        return executor.isShutdown();
    }

    public ThreadPoolExecutor getExecutor() {
        return (ThreadPoolExecutor) executor;
    }
}

7.3.2 修改 HttpProcessor 类以支持多线程

HttpProcessor 类将实现 Runnable 接口,以便能够作为任务提交给线程池。每个客户端请求将由线程池中的一个独立线程来处理,从而避免阻塞主线程。

public class HttpProcessor implements Runnable {
    private Socket socket;

    private final static ServletProcessor servletProcessor = new ServletProcessor();

    private final static StaticResourceProcessor staticProcessor = new StaticResourceProcessor();

    public HttpProcessor(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        // 处理请求并返回响应
        process();
    }
    // ...... 其他代码保持不变
}

7.3.3 修改 HttpConnector 类以启动线程池

HttpConnector 类负责启动服务器监听端口,并利用初始化好的线程池来处理客户端连接请求。

package com.daicy.minitomcat;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpConnector implements Runnable {
    private static final int PORT = 8080;

    // 创建一个线程池,最多支持 10 个并发请求
    private static ThreadPool threadPool = new ThreadPool(10);

    public void start() {
        Thread thread = new Thread(this);
        thread.start();
    }

    @Override
    public void run() {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            System.out.println("HTTP Connector is running on port " + PORT);

            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("Accepted connection from " + clientSocket.getInetAddress());

                // 将连接交给 HttpProcessor 处理,并提交给线程池
                HttpProcessor processor = new HttpProcessor(clientSocket);
                threadPool.submitTask(processor);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

7.4 测试

完成代码修改后,请按以下步骤验证多线程支持是否生效:

  1. 启动服务器:运行主程序,确保服务器正常启动且无报错。
  2. 并发访问:使用多个浏览器标签页或不同的 HTTP 客户端工具,同时访问不同的路径(如静态资源与 Servlet 接口)。
  3. 观察日志:检查控制台日志,确认多个请求被同时接收和处理,且没有出现请求阻塞或排队等待的现象。
  4. 性能验证:观察服务器的响应时间,确保在高并发下线程池有效提升了吞吐能力。

7.5 学习收获

通过本章的实现,我们获得了以下技术经验:

  • 线程池应用:掌握了线程池的核心概念,学会了如何使用 ExecutorServiceThreadPoolExecutor 管理线程,避免频繁创建销毁线程带来的性能开销。
  • 多线程编程:深入理解了如何在 Web 服务器架构中引入多线程,实现请求的并发处理,从而提升服务器的吞吐量和响应速度。
  • 并发安全设计:学习了如何设计并发安全的服务器结构,确保多个请求在同一时刻处理时互不干扰,数据状态一致。

项目源代码地址:

https://github.com/daichangya/MiniTomcat/tree/chapter7/mini-tomcat

说明:本章代码基于 Java 并发包(java.util.concurrent)实现,适用于 Java 5 及以上版本。示例中的线程池配置主要用于学习与演示,在生产环境中建议根据实际负载情况调整线程池参数,并注意资源泄露风险。