线程池的工作原理

发布于:2025-04-06 ⋅ 阅读:(12) ⋅ 点赞:(0)
  • 固定线程池:线程池中的线程数是固定的,线程池创建时就已经设定了固定的线程数量。在任务提交时,线程池会将任务分配给空闲的线程执行。如果所有线程都在执行任务,新的任务会被放到任务队列中,直到有线程空闲出来。

  • 线程复用:线程池中的线程会被复用,也就是说,线程池中的线程在任务执行完后并不会销毁,而是会被复用来执行其他任务。这能够提高任务的处理效率,避免了频繁创建和销毁线程的开销。

  • 队列管理:如果提交的任务超过了线程池中线程的数量,任务会被放入任务队列中。队列有大小限制,超出限制时会根据拒绝策略来处理(例如抛出异常、丢弃任务等)。

代码示例

import java.util.concurrent.*;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(13); // 创建一个固定大小为 13 的线程池

        // 提交多个任务
        for (int i = 0; i < 20; i++) {
            int taskId = i;
            executorService.execute(() -> {
                System.out.println("任务 " + taskId + " 开始执行, 线程: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模拟任务执行过程
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println("任务 " + taskId + " 执行完成");
            });
        }

        executorService.shutdown(); // 关闭线程池
    }
}

代码解释:

  1. 创建线程池Executors.newFixedThreadPool(13) 创建一个固定大小为 13 的线程池。线程池可以同时执行 13 个任务。

  2. 提交任务:通过 executorService.execute() 方法提交 20 个任务,线程池会把这些任务分配给空闲的线程来执行。如果有超过 13 个任务,其他任务会被放入任务队列中,等待有线程空闲时再执行。

  3. 关闭线程池executorService.shutdown() 会关闭线程池,不再接受新的任务。所有已经提交的任务会继续执行,直到执行完毕。

注意事项:

  • 线程池大小:根据任务的数量和执行的需求,线程池大小应该合理设置。创建过多的线程会导致资源竞争和上下文切换的开销;线程池太小则可能导致任务堆积和延迟执行。

  • 关闭线程池:通过 shutdown()shutdownNow() 来关闭线程池,建议在所有任务完成后关闭线程池。如果线程池不再使用,及时关闭能够释放资源。

  • 任务队列:线程池会使用一个任务队列来缓存那些等待执行的任务。如果任务队列满了,你可能会遇到任务拒绝的情况。你可以通过设置自定义的拒绝策略来应对这种情况。

扩展内容

定时任务池

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

这行代码的作用是创建一个大小为 1 的定时任务线程池。ScheduledExecutorServiceExecutorService 的一个子接口,它提供了调度任务的功能,包括定时任务和周期性任务的执行。

关键点:

  1. 定时任务ScheduledExecutorService 允许你以固定的延迟时间执行任务,或者按照固定的时间间隔周期性地执行任务。

  2. 线程池大小:在这个例子中,线程池的大小为 1,意味着线程池中只有一个线程可用于执行任务。如果你提交多个定时任务,它们会依次排队执行。只有一个线程的线程池适用于那些对并发要求不高的任务。

  3. 常见方法

    • schedule(): 用于在指定延迟后执行一个任务。

    • scheduleAtFixedRate(): 用于按固定频率执行任务,即每隔一定时间执行一次,适用于周期性任务。

    • scheduleWithFixedDelay(): 用于在执行完一个任务后延迟一定时间再执行下一个任务,适用于任务间有延迟需求的情况。

代码示例:

import java.util.concurrent.*;

public class ScheduledExecutorServiceExample {
    public static void main(String[] args) {
        // 创建一个定时任务线程池,池中只有一个线程
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

        // 使用 schedule() 方法定时执行任务
        scheduledExecutorService.schedule(() -> {
            System.out.println("任务执行了,延迟 2 秒");
        }, 2, TimeUnit.SECONDS);  // 延迟 2 秒执行任务

        // 使用 scheduleAtFixedRate() 方法按固定频率执行任务
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("周期性任务执行,每 3 秒执行一次");
        }, 0, 3, TimeUnit.SECONDS);  // 初始延迟 0 秒,之后每 3 秒执行一次

        // 使用 scheduleWithFixedDelay() 方法按固定延迟执行任务
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            System.out.println("任务执行完后,延迟 1 秒再执行下一个任务");
        }, 0, 1, TimeUnit.SECONDS);  // 初始延迟 0 秒,任务之间的间隔 1 秒

        // 模拟主线程等待一段时间后关闭线程池
        try {
            Thread.sleep(10000);  // 等待 10 秒,观察任务执行情况
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        scheduledExecutorService.shutdown();  // 关闭线程池
    }
}

代码解释:

  1. schedule() 方法:用于指定一个任务在指定延迟后执行。这里我们设置了延迟 2 秒后执行任务。

  2. scheduleAtFixedRate() 方法:用于周期性地执行任务,初始延迟为 0 秒,每 3 秒执行一次。

  3. scheduleWithFixedDelay() 方法:用于按固定的延迟时间执行任务,任务执行完成后会延迟 1 秒再执行下一个任务。

  4. shutdown():在所有任务完成后,调用 shutdown() 方法来关闭线程池,避免资源泄露。

注意事项:

  • 线程池的大小:由于线程池的大小为 1,所有提交的任务将依次排队执行。如果有多个周期性任务,它们将按顺序执行,无法并行。如果需要并行执行多个任务,可以增加线程池的大小。

  • 任务提交方式:你可以根据需要选择使用 schedule(), scheduleAtFixedRate(), 或 scheduleWithFixedDelay() 方法。选择合适的方式取决于任务的特性,比如是否是周期性任务,是否需要固定的时间间隔等。

  • 关闭线程池:使用 shutdown()shutdownNow() 来关闭线程池。要确保所有任务执行完之后再关闭线程池,避免任务被中断。

执行任务方式

executorService.execute(() -> { ... }) 是 Java 中使用线程池执行任务的一种方式,它是 ExecutorService 接口中的方法之一。这个方法接受一个实现了 Runnable 接口的任务,并将其提交给线程池执行。

在这个例子中,execute() 方法会提交一个无返回值的任务(即 Runnable)给线程池执行。当你使用 Lambda 表达式时,() -> { ... } 是一种简洁的写法,表示创建一个匿名 Runnable 实现类。

ExecutorService executorService = Executors.newFixedThreadPool(2); // 创建一个包含两个线程的线程池

executorService.execute(() -> {
    System.out.println("任务开始执行");
    // 任务逻辑
    try {
        Thread.sleep(1000); // 模拟任务执行过程中的等待
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    System.out.println("任务执行完成");
});

executorService.shutdown(); // 关闭线程池

关键点:

  • execute() 方法不会返回值,适用于那些没有返回结果的任务。如果你需要获取任务的执行结果,应该使用 submit() 方法,它返回一个 Future 对象。

  • Runnable 接口代表一个无返回值的任务,你可以通过 Lambda 表达式或者匿名内部类的方式来实现。

  • ExecutorServiceExecutor 接口的子接口,提供了更多的控制方法,如 shutdown() 用来关闭线程池。

注意事项:

  1. 如果线程池已经被关闭,或者正在终止,调用 execute() 提交任务时会抛出 RejectedExecutionException

eg:

  1. 如果你希望在任务执行后获取结果,使用 submit() 而不是 execute(),因为 submit() 返回一个 Future 对象,允许你获取执行结果或者捕获异常。