1. 引言

本文将深入对比两种常见的服务器线程模型:每连接一线程(Thread-per-Connection)每请求一线程(Thread-per-Request)。首先明确"连接"和"请求"的定义,然后通过基于Socket的Java服务器实现两种模型,最后总结关键要点。

2. 连接与请求的线程模型

先给出核心定义:

  • 线程模型:程序创建和同步线程以实现并发和多任务的方法
  • HTTP连接:客户端与服务器间的TCP通道
  • HTTP请求:连接上的一次具体操作

当客户端需要与服务器通信时:

  • ✅ 新建TCP连接 → 发起HTTP请求
  • ✅ 复用现有连接(Keep-Alive机制,HTTP 1.1默认启用)

两种模型的本质区别:

  • 每连接一线程:整个连接生命周期使用同一线程
  • 每请求一线程:每个请求独立分配线程(无论是否复用连接)

两种线程模型对比

⚠️ 为简化示例,后续代码省略真实服务器架构中的优化(如线程池)

3. 每连接一线程模型详解

每个客户端连接独占一个线程,该线程处理该连接的所有请求。实现示例:

public class ThreadPerConnectionServer {
   
   private static final int PORT = 8080;
   
   public static void main(String[] args) {
      try (ServerSocket serverSocket = new ServerSocket(PORT)) {
         logger.info("Server started on port {}", PORT);
            while (!serverSocket.isClosed()) {
               try {
                  Socket newClient = serverSocket.accept();
                  logger.info("New client connected: {}", newClient.getInetAddress());
                  ClientConnection clientConnection = new ClientConnection(newClient);
                  new ThreadPerConnection(clientConnection).start();
               } catch (IOException e) {
                  logger.error("Error accepting connection", e);
               }
            }
         } catch (IOException e) {
            logger.error("Error starting server", e);
         }
      }
   }
}

ClientConnection 是资源包装器:

public class ClientConnection implements Closeable {

   // ...

   public ClientConnection(Socket socket) throws IOException {
      this.socket = socket;
      this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      this.writer = new PrintWriter(socket.getOutputStream(), true);
    }

   @Override
   public void close() throws IOException {
      try (Writer writer = this.writer; Reader reader = this.reader; Socket socket = this.socket) {
         // 自动关闭所有资源
      }
   }
}

核心线程处理逻辑:

public class ThreadPerConnection extends Thread {
   // ...
   
   @Override
   public void run() {
      try (ClientConnection client = this.clientConnection) {
         String request;
         while ((request = client.getReader().readLine()) != null) {
            Thread.sleep(1000); // 模拟业务处理
            logger.info("Processing request: {}", request);
            clientConnection.getWriter()
               .println("HTTP/1.1 200 OK - Processed request: " + request);
         }
      } catch (Exception e) {
         logger.error("Error processing request", e);
      }
   }
}

核心优势

  • ✅ 实现简单粗暴
  • ✅ 避免线程上下文切换(同一连接请求由同一线程处理)
  • ❌ 连接数直接限制并发能力(N个连接需N个线程)

4. 每请求一线程模型详解

每个请求独立分配线程,即使来自同一连接。实现示例:

public class ThreadPerRequestServer {
   //...
   
   public static void main(String[] args) {
      List<ClientConnection> clientConnections = new ArrayList<>();
      try (ServerSocket serverSocket = new ServerSocket(PORT)) {
         logger.info("Server started on port {}", PORT);
         while (!serverSocket.isClosed()) {
            acceptNewConnections(serverSocket, clientConnections);
            handleRequests(clientConnections);
         }
      } catch (IOException e) {
         logger.error("Server error: {}", e.getMessage());
      } finally {
         closeClientConnections(clientConnections);
      }
   }
}

连接管理方法:

private static void acceptNewConnections(ServerSocket serverSocket, List<ClientConnection> clientConnections) throws SocketException {
   serverSocket.setSoTimeout(100); // 设置超时避免阻塞
   try {
      Socket newClient = serverSocket.accept();
      ClientConnection clientConnection = new ClientConnection(newClient);
      clientConnections.add(clientConnection);
      logger.info("New client connected: {}", newClient.getInetAddress());
   } catch (IOException ignored) {
      // 超时异常忽略
   }
}

请求处理核心逻辑:

private static void handleRequests(List<ClientConnection> clientConnections) throws IOException {
   Iterator<ClientConnection> iterator = clientConnections.iterator();
   while (iterator.hasNext()) {
      ClientConnection client = iterator.next();
      if (client.getSocket().isClosed()) {
         logger.info("Client disconnected: {}", client.getSocket().getInetAddress());
         iterator.remove();
         continue;
      }
      try {
         BufferedReader reader = client.getReader();
         if (reader.ready()) {
            String request = reader.readLine();
            if (request != null) {
               new ThreadPerRequest(client.getWriter(), request).start();
            }
         }
      } catch (IOException e) {
         logger.error("Error reading from client {}", client.getSocket().getInetAddress(), e);
      }
   }
}

单请求线程实现:

public class ThreadPerRequest extends Thread {

   //...

   @Override
   public void run() {
      try {
         Thread.sleep(1000); // 模拟业务处理
         logger.info("Processing request: {}", request);
         writer.println("HTTP/1.1 200 OK - Processed request: " + request);
      } catch (Exception e) {
         logger.error("Error processing request: {}", e.getMessage());
      }
   }
}

关键特点

  • ✅ 理论上无连接数限制
  • ✅ 适合突发流量场景
  • ❌ 高频率线程创建/销毁
  • ❌ 上下文切换开销大

💡 生产环境通常配合线程池优化(本文为简化演示未使用)

5. 核心对比总结

特性 每连接一线程 每请求一线程
线程生命周期 长期存活(连接关闭才终止) 短期存活(请求完成即终止)
上下文切换负载 低(受连接数限制) 高(每个请求切换)
扩展性 有限(依赖最大连接数) 强(理论上无限)
适用场景 连接数可预测的应用 请求量波动的应用

核心结论

  • 每连接一线程:适合连接数稳定、追求简单实现的场景(如内部服务)
  • 每请求一线程:适合互联网流量不可预测的场景(如电商平台大促)

⚠️ 踩坑提醒:JVM最大线程数N的限制下:

  • 每连接模型:最多支持N个并发客户端
  • 每请求模型:最多支持N个并发请求(客户端需排队等待)

6. 结论

选择线程模型需结合具体需求:

  • 每连接一线程:简单高效,适合已知客户端数量的场景
  • 每请求一线程:灵活扩展,适合流量波动的互联网应用

生产环境建议:现代服务器(如Netty)通过事件驱动模型规避了这两种传统模型的缺陷,值得深入学习。

示例代码已上传至GitHub仓库


原始标题:Thread per Connection vs Thread per Request | Baeldung