Java 线程池(Thread Pools)

更新于 2025-12-29

Jakob Jenkov 2023-01-24

线程池是一组可以“复用”来执行任务的线程,使得每个线程可以执行多个任务。线程池是为每个任务都创建新线程的一种替代方案。

相比于为每个任务都创建一个新线程,复用已存在的线程能显著减少性能开销。因此,使用已有线程来执行任务通常可以获得更高的整体吞吐量。

此外,使用线程池可以更方便地控制同时活跃的线程数量。每个线程都会消耗一定的系统资源(例如内存 RAM),如果同时激活的线程过多,可能导致系统资源耗尽(比如操作系统因内存不足而开始将内存交换到磁盘),从而导致计算机变慢。

在本线程池教程中,我将解释线程池的工作原理、使用场景,以及如何在 Java 中实现一个线程池。需要注意的是,Java 已经内置了线程池——即 java.util.concurrent.ExecutorService,所以你无需自己从头实现。不过,有时你可能希望自定义线程池,以支持 ExecutorService 所不具备的功能;或者,你也可以把实现自己的线程池当作一次学习体验。

线程池的工作原理

线程池不会为每个并发任务都启动一个新线程,而是将任务提交给线程池。只要池中有空闲线程,任务就会被分配给其中一个并执行。

内部实现上,这些任务会被插入到一个 阻塞队列Blocking Queue)中,而线程池中的线程会不断地从该队列中取出任务执行。当一个新任务被加入队列时,某个空闲线程会成功将其取出并执行,其余空闲线程则继续阻塞等待下一个任务。

线程池工作示意图


线程池的使用场景

线程池常用于多线程服务器中。每当有新的网络连接到达服务器时,该连接会被封装成一个任务,并提交给线程池。线程池中的线程会并发处理这些连接上的请求。

后续教程将详细介绍如何在 Java 中实现多线程服务器。


Java 内置的线程池

Java 在 java.util.concurrent 包中已经提供了内置的线程池,因此你不需要自己实现。 尽管如此,了解线程池的内部实现仍然是有益的。


Java 线程池的实现

下面是一个简单的线程池实现。该实现使用了 Java 5 起就提供的标准 java.util.concurrent.BlockingQueue

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ThreadPool {
    private BlockingQueue<Runnable> taskQueue = null;
    private List<PoolThreadRunnable> runnables = new ArrayList<>();
    private boolean isStopped = false;

    public ThreadPool(int noOfThreads, int maxNoOfTasks) {
        taskQueue = new ArrayBlockingQueue<>(maxNoOfTasks);
        for (int i = 0; i < noOfThreads; i++) {
            PoolThreadRunnable poolThreadRunnable = new PoolThreadRunnable(taskQueue);
            runnables.add(poolThreadRunnable);
        }
        for (PoolThreadRunnable runnable : runnables) {
            new Thread(runnable).start();
        }
    }

    public synchronized void execute(Runnable task) throws Exception {
        if (this.isStopped)
            throw new IllegalStateException("ThreadPool is stopped");
        this.taskQueue.offer(task);
    }

    public synchronized void stop() {
        this.isStopped = true;
        for (PoolThreadRunnable runnable : runnables) {
            runnable.doStop();
        }
    }

    public synchronized void waitUntilAllTasksFinished() {
        while (this.taskQueue.size() > 0) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

以下是 PoolThreadRunnable 类,它实现了 Runnable 接口,因此可以被 Java 线程执行:

import java.util.concurrent.BlockingQueue;

public class PoolThreadRunnable implements Runnable {
    private Thread thread = null;
    private BlockingQueue<Runnable> taskQueue = null;
    private boolean isStopped = false;

    public PoolThreadRunnable(BlockingQueue<Runnable> queue) {
        taskQueue = queue;
    }

    public void run() {
        this.thread = Thread.currentThread();
        while (!isStopped()) {
            try {
                Runnable runnable = taskQueue.take();
                runnable.run();
            } catch (Exception e) {
                // 记录或报告异常,但保持线程存活
            }
        }
    }

    public synchronized void doStop() {
        isStopped = true;
        // 中断线程,使其从 taskQueue.take() 的阻塞中退出
        this.thread.interrupt();
    }

    public synchronized boolean isStopped() {
        return isStopped;
    }
}

最后,这是使用上述 ThreadPool 的示例:

public class ThreadPoolMain {
    public static void main(String[] args) throws Exception {
        ThreadPool threadPool = new ThreadPool(3, 10);
        for (int i = 0; i < 10; i++) {
            int taskNo = i;
            threadPool.execute(() -> {
                String message = Thread.currentThread().getName() + ": Task " + taskNo;
                System.out.println(message);
            });
        }
        threadPool.waitUntilAllTasksFinished();
        threadPool.stop();
    }
}

实现说明

该线程池实现包含两个部分:

  • ThreadPool 类:对外暴露的线程池接口;
  • PoolThreadRunnable 类:代表实际执行任务的线程。

执行任务

调用 ThreadPool.execute(Runnable r) 方法,传入一个 Runnable 实例。该任务会被加入内部的阻塞队列,等待空闲线程取出执行。

任务执行流程

PoolThreadRunnable.run() 方法会不断从队列中取出任务并执行。执行完一个任务后,线程会循环尝试获取下一个任务,直到线程池被停止。

停止线程池

调用 ThreadPool.stop() 方法会设置 isStopped 标志,并对每个工作线程调用 doStop()。这会中断每个线程(通过 thread.interrupt()),使其从 taskQueue.take() 的阻塞状态中退出。此时线程会捕获 InterruptedException,检查 isStopped 标志,发现为 true 后便退出 run() 方法,线程随之终止。

注意:execute() 方法在 stop() 被调用后会抛出 IllegalStateException,防止向已关闭的线程池提交新任务。