1、什么是线程池

线程池是一种用于管理和调度线程执行的机制,它可以帮助我们更有效地利用系统资源,提高多线程编程的性能和可维护性。在多线程应用程序中,创建和销毁线程是一项昂贵的操作,线程池可以通过重复使用线程来减少这种开销,从而提高应用程序的性能。

Java线程池框架在java.util.concurrent包中提供了一些类来实现线程池,其中最常用的类是ExecutorService接口和ThreadPoolExecutor类。以下是一些与Java线程池相关的重要概念和用法:

  1. 创建线程池: 可以使用Executors类提供的静态工厂方法来创建不同类型的线程池,例如newFixedThreadPoolnewCachedThreadPoolnewSingleThreadExecutor等。
  2. ExecutorService接口: ExecutorService是一个表示线程池的接口,它提供了提交任务、管理线程池状态以及获取结果的方法。常见的方法包括submit(提交可执行任务)、invokeAll(提交一组任务并等待全部完成)、invokeAny(提交一组任务并等待任意一个完成)等。
  3. ThreadPoolExecutor类: 这是一个实现了ExecutorService接口的类,它提供了更多的配置选项和灵活性。可以通过ThreadPoolExecutor的构造函数来创建一个自定义配置的线程池。
  4. 核心线程数和最大线程数: 线程池中通常会包含一组核心线程,它们会一直保持活动状态。如果任务数量超过了核心线程数,线程池可以创建更多的线程,但数量不会超过最大线程数限制。超过核心线程数的线程被称为“非核心线程”。
  5. 工作队列: 当线程池的核心线程都在忙碌时,新的任务会被放入工作队列中等待执行。ThreadPoolExecutor支持不同类型的工作队列,如有界队列和无界队列。
  6. 拒绝策略: 如果工作队列已满且线程池中的线程数已达到最大限制,则新的任务可能会被拒绝执行。可以通过指定拒绝策略来处理这种情况,例如抛出异常、丢弃任务等。
  7. 线程池的生命周期: 线程池可以通过调用shutdown方法来优雅地关闭,这会等待所有已提交的任务完成执行。还可以使用shutdownNow方法尝试立即停止所有线程。

使用线程池时,需要根据应用程序的需求和性能要求来选择适当的线程池类型、核心线程数、工作队列以及拒绝策略。这样可以最大程度地提高多线程应用程序的效率和可靠性。

2、创建线程池

FixedThreadPool(固定大小线程池)

这种线程池维护一个固定数量的线程,不会根据任务的数量进行调整。适用于预先知道任务数量并希望限制并发线程数的情况。

示例代码:

ExecutorService executor = Executors.newFixedThreadPool(5); 
executor.submit(new MyTask()); 
executor.shutdown();

CachedThreadPool(缓存线程池)

这种线程池可以根据任务数量动态地创建线程,如果线程池中没有可用线程,则会创建一个新线程;如果线程空闲时间过长,线程会被回收。适用于任务数量不确定,需要自动适应的场景。

示例代码:

ExecutorService executor = Executors.newCachedThreadPool(); 
executor.submit(new MyTask()); 
executor.shutdown();

 SingleThreadExecutor(单线程线程池)

这种线程池只维护一个单独的线程来执行任务,保证所有任务按照顺序执行。适用于需要顺序执行任务的场景,或者需要在单个线程中处理特定资源的情况。

示例代码:

ExecutorService executor = Executors.newSingleThreadExecutor(); 
executor.submit(new MyTask()); 
executor.shutdown();

 ScheduledThreadPool(定时任务线程池)

这种线程池用于执行定时任务和周期性任务。它允许按照指定的时间间隔来执行任务,并可以设定首次执行的延迟时间。

示例代码:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(3); 
executor.schedule(new MyTask(), 1, TimeUnit.SECONDS); // 延迟1秒执行任务
executor.shutdown();

在实际应用中,除了上述标准线程池之外,还可以使用ThreadPoolExecutor类来创建自定义线程池,以满足特定的需求。通过设置核心线程数、最大线程数、工作队列等参数,可以根据应用的需求进行更细粒度的线程池配置。这些线程池的选择取决于我们的应用程序的性能要求、任务类型和并发情况。

自定义线程

如果不想使用Executors类的工厂方法来创建线程池,可以直接使用ThreadPoolExecutor类来创建newFixedThreadPool线程池。以下是使用ThreadPoolExecutor创建固定大小线程池的示例代码:

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ArrayBlockingQueue;

public class CustomFixedThreadPoolExample {
    public static void main(String[] args) {
        int corePoolSize = 5; // 核心线程数
        int maxPoolSize = 5;  // 最大线程数
        long keepAliveTime = 0L; // 非核心线程的空闲时间
        TimeUnit timeUnit = TimeUnit.MILLISECONDS; // 时间单位
        ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10); // 工作队列

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            corePoolSize, maxPoolSize, keepAliveTime, timeUnit, workQueue
        );

        // 提交任务
        for (int i = 0; i < 20; i++) {
            executor.submit(new MyTask(i));
        }

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

    static class MyTask implements Runnable {
        private final int taskId;

        public MyTask(int taskId) {
            this.taskId = taskId;
        }

        @Override
        public void run() {
            System.out.println("Task " + taskId + " is being executed by " + Thread.currentThread().getName());
        }
    }
}

在这个示例中,我们使用ThreadPoolExecutor的构造函数来创建一个固定大小的线程池,通过设置核心线程数、最大线程数、工作队列等参数来定义线程池的特性。然后,我们提交了20个任务到线程池中,每个任务都会被分配给一个线程执行。最后,我们关闭了线程池。

需要注意的是,自定义线程池时要根据实际需求来设置参数,以确保线程池能够在不同的负载情况下正常工作。


3、工作队列

工作队列(Work Queue)是线程池中用于存放等待执行的任务的缓冲区。当线程池中的核心线程都在忙碌时,新提交的任务会被放入工作队列中等待执行。工作队列对于控制线程池的并发度和任务调度非常重要。Java线程池中常见的工作队列类型有以下几种:

  1. 无界队列(Unbounded Queue): 无界队列可以容纳任意数量的任务,这意味着即使没有空闲线程,新的任务也会被加入队列。常用的无界队列有LinkedBlockingQueue。使用无界队列时,可能会导致内存消耗过大,因为队列中可能累积大量未执行的任务。
  2. 有界队列(Bounded Queue): 有界队列有一个最大容量,超过这个容量时,新的任务将无法加入队列,而是会触发拒绝策略。常用的有界队列有ArrayBlockingQueue。使用有界队列可以控制任务的排队数量,从而避免无限制的内存增长。
  3. 优先级队列(Priority Queue): 优先级队列会根据任务的优先级对任务进行排序,每次取出优先级最高的任务执行。Java中的线程池通常不直接支持优先级队列,但可以通过自定义任务调度器来实现。

可以通过修改工作队列的类型来调整线程池的行为。例如,如果在任务数量暴增时能够有一定的缓冲,可以选择有界队列来控制排队的任务数量。如果希望在某些情况下尽可能地接受任务,可以使用无界队列。不同类型的工作队列适用于不同的应用场景,取决于对任务排队、内存使用和任务调度的需求。

在创建线程池时,可以根据需要通过构造函数或配置方法来指定工作队列的类型和参数。例如,使用 ThreadPoolExecutor 类,可以这样创建一个固定大小的线程池,并指定有界队列:

import java.util.concurrent.*;

public class CustomThreadPoolExample {
    public static void main(String[] args) {
        int corePoolSize = 5;
        int maxPoolSize = 10;
        long keepAliveTime = 0L;
        TimeUnit timeUnit = TimeUnit.MILLISECONDS;
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(20); // 使用有界队列

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            corePoolSize, maxPoolSize, keepAliveTime, timeUnit, workQueue
        );

        // 提交任务
        for (int i = 0; i < 20; i++) {
            executor.submit(new MyTask(i));
        }

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

    static class MyTask implements Runnable {
        private final int taskId;

        public MyTask(int taskId) {
            this.taskId = taskId;
        }

        @Override
        public void run() {
            System.out.println("Task " + taskId + " is being executed by " + Thread.currentThread().getName());
        }
    }
}

在这个示例中,我们使用了 ArrayBlockingQueue 作为有界队列,通过构造函数传递给了 ThreadPoolExecutor。这样,线程池会在有界队列中存放等待执行的任务。根据应用需求,可以选择适合的队列类型来实现任务的调度和排队策略。


4、拒绝策略

拒绝策略(Rejection Policy)是在线程池的工作队列已满且线程数已达到最大限制时,决定如何处理新提交的任务的策略。当线程池无法接受新任务时,就会触发拒绝策略来决定如何处理这些被拒绝的任务。Java中的线程池框架提供了几种常见的拒绝策略选择。

以下是一些常见的拒绝策略以及它们的解释:

  1. Abort Policy(默认策略): 这是默认的拒绝策略,当工作队列已满时,新的任务会直接抛出 RejectedExecutionException 异常,不会执行任务。
  2. Caller Runs Policy: 在调用者的线程中直接执行被拒绝的任务。这样,提交任务的线程会执行被拒绝的任务,从而提供了一种简单的降级策略,确保不会丢失任务。
  3. Discard Policy: 直接丢弃被拒绝的任务,不抛出任何异常。这可能会导致一些任务被默默地丢失,因此要确保被丢弃的任务不会影响系统正常运行。
  4. Discard Oldest Policy: 丢弃工作队列中最旧的任务,然后尝试将新的任务加入队列。这可以保证工作队列中都是相对新的任务,但也可能导致一些任务被丢弃。

在 Java 的 ThreadPoolExecutor 类中,可以通过调用 setRejectedExecutionHandler 方法来设置拒绝策略。以下是一个示例,展示如何使用不同的拒绝策略来创建线程池:

import java.util.concurrent.*;

public class RejectionPolicyExample {
    public static void main(String[] args) {
        int corePoolSize = 5;
        int maxPoolSize = 10;
        long keepAliveTime = 0L;
        TimeUnit timeUnit = TimeUnit.MILLISECONDS;
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(5); // 有界队列

        RejectedExecutionHandler rejectHandler = new ThreadPoolExecutor.AbortPolicy(); // 使用 Abort Policy

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            corePoolSize, maxPoolSize, keepAliveTime, timeUnit, workQueue, rejectHandler
        );

        // 提交任务
        for (int i = 0; i < 20; i++) {
            executor.submit(new MyTask(i));
        }

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

    static class MyTask implements Runnable {
        private final int taskId;

        public MyTask(int taskId) {
            this.taskId = taskId;
        }

        @Override
        public void run() {
            System.out.println("Task " + taskId + " is being executed by " + Thread.currentThread().getName());
        }
    }
}

在上述示例中,我们创建了一个固定大小的线程池,并使用了有界队列作为工作队列。我们通过 ThreadPoolExecutor.AbortPolicy() 设置了拒绝策略为默认的 Abort Policy。可以尝试将 rejectHandler 更换为其他拒绝策略,以查看不同策略的效果。

根据应用需求,选择适合的拒绝策略可以保障系统的稳定性和可靠性。


5、实现

在Spring Cloud中,可以使用@Configuration类来创建自定义的线程池,并将其配置为Spring的Bean,以便在应用中使用。下面是一个基于Spring Cloud的示例,演示如何创建一个自定义线程池:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableAsync
public class CustomThreadPoolConfig {

    @Bean(name = "customThreadPool")
    public ThreadPoolTaskExecutor customThreadPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        
        // 设置核心线程数和最大线程数
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        
        // 设置线程空闲超时时间
        executor.setKeepAliveSeconds(60);
        
        // 设置队列为有界队列,容量为10
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10);
        executor.setQueueCapacity(10);
        executor.setRejectedExecutionHandler(new CustomRejectedExecutionHandler());
        
        executor.setThreadNamePrefix("custom-thread-");
        executor.initialize();
        return executor;
    }

    // 自定义拒绝策略
    static class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            // 在被拒绝时执行自定义操作
            System.out.println("Task rejected: " + r.toString());
        }
    }
}

在上述示例中,我们使用@Configuration注解创建了一个配置类CustomThreadPoolConfig。通过在类上添加@EnableAsync注解,我们启用了Spring的异步支持。然后,我们创建了一个名为 customThreadPool 的自定义线程池。通过设置核心线程数、最大线程数、线程空闲超时时间等参数来配置线程池的基本特性。我们还设置了有界队列,使用 LinkedBlockingQueue,并设置了队列容量为 10。同时,我们创建了一个自定义的拒绝策略 CustomRejectedExecutionHandler,它在任务被拒绝时会输出一条提示信息。

一旦配置完成,可以在应用中使用这个自定义线程池。例如,可以在需要异步执行的方法上添加@Async("customThreadPool")注解,指定要使用的线程池。

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @Async("customThreadPool")
    public void asyncMethod() {
        // 异步执行的任务逻辑
    }
}

在这个示例中,asyncMethod方法将会使用我们之前定义的名为customThreadPool的线程池来执行异步任务。

Spring Cloud应用中适当地配置和使用自定义线程池,以满足应用的性能需求和异步任务的并发需求。