一文读懂Java线程池,强力推荐

Java****线程池详解

  1. 线程池基础

1.1 什么是线程池

1.2 为什么需要线程池

1.3 线程池的核心参数

1.4 线程池工作原理

  1. Java中的线程池类型与使用

2.1 常见的线程池类型

2.1.1 FixedThreadPool

2.1.2 CachedThreadPool

2.1.3 SingleThreadExecutor

2.1.4 ScheduledThreadPool

2.2 线程池的使用示例

2.2.1 FixedThreadPool的使用

2.2.2 CachedThreadPool的使用

2.2.3 SingleThreadExecutor的使用

2.2.4 ScheduledThreadPool的使用

2.3 线程池的优缺点

2.3.1 线程池的优点

2.3.2 线程池的缺点

  1. ThreadPoolExecutor详解

3.1 ThreadPoolExecutor的生命周期

3.2 ThreadPoolExecutor执行流程

3.3 线程池参数调优

3.3.1 核心线程数和最大线程数

3.3.2 工作队列的选择

3.3.3 拒绝策略的选择

  1. 阻塞队列与线程池的关系

4.1 常用阻塞队列类型

4.1.1 ArrayBlockingQueue

4.1.2 LinkedBlockingQueue

4.1.3 SynchronousQueue

4.1.4 PriorityBlockingQueue

4.1.5 DelayQueue

4.2 阻塞队列对线程池行为的影响

4.2.1 有界队列

4.2.2 无界队列

4.2.3 同步队列

4.3 队列选择指南

4.4 阻塞队列性能对比

  1. 线程池监控与管理

5.1 内置监控功能

5.2 JMX监控

5.3 自定义线程池监控器

5.4 动态调整线程池配置

5.5 线程池监控最佳实践

  1. 线程池常见问题与解决方案

6.1 线程池死锁与死锁检测

解决方案:

6.2 任务拒绝处理

自定义拒绝策略

6.3 线程泄漏问题场景一:忘记关闭线程池

场景二:线程被阻塞且无法中断

6.4 线程池性能问题与调优

常见性能问题

性能调优策略

6.5 在容器环境中使用线程池的注意事项

问题

解决方案

  1. 线程池最佳实践

7.1 线程池创建与配置最佳实践

避免使用Executors工厂方法

线程池命名

线程池分离原则

7.2 任务提交最佳实践

使用合适的任务提交方法

避免任务依赖

设置任务超时

推荐使用CompletableFuture

7.3 异常处理最佳实践

处理未捕获的异常

异常统计与监控

7.4 关闭线程池最佳实践

优雅地关闭线程池

线程池关闭时的资源清理

7.5 生产环境中的线程池监控

关键监控指标

监控实现

7.6 线程池适用场景与不适用场景

适用场景

不适用场景

  1. 线程池在框架中的应用

8.1 Spring框架中的线程池

TaskExecutor接口

Spring中的线程池配置

Spring的异步执行

Spring的定时任务调度

8.2 Tomcat中的线程池

Tomcat的线程模型

Tomcat线程池配置

Spring Boot中配置Tomcat线程池

Tomcat线程池的监控

8.3 Netty中的线程池

Netty的线程模型

自定义Netty线程池配置

使用业务线程池

8.4 MyBatis中的线程池

MyBatis的Executor

MyBatis-Spring与数据库连接池

8.5 RocketMQ中的线程池

RocketMQ的线程池架构

RocketMQ消费者线程池配置

自定义RocketMQ生产者的线程池

  1. 高级主题

9.1 ForkJoinPool与工作窃取算法

9.1.1 ForkJoinPool原理

9.1.2 ForkJoinPool vs ThreadPoolExecutor9.1.3 工作窃取算法

9.1.4 RecursiveTask和RecursiveAction

9.2 CompletableFuture与异步编程

9.2.1 CompletableFuture基础

9.2.2 组合多个CompletableFuture

9.2.3 CompletableFuture与线程池配合使用

9.3 Java 19虚拟线程与结构化并发

9.3.1 虚拟线程基础

9.3.2 虚拟线程与传统线程池的比较

9.3.3 结构化并发

9.3.4 虚拟线程最佳实践

9.4 高并发系统中的线程池优化

9.4.1 线程池隔离策略

9.4.2 动态线程池

9.4.3 断路器模式与线程池

9.4.4 队列背压与限流

  1. 总结与展望

10.1 核心知识回顾

10.2 线程池发展趋势

10.3 后续学习路径

1. 线程池基础

1.1 什么是线程池

线程池是一种线程使用模式。线程池维护多个线程,等待着监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程的开销,另一方面避免了线程数量膨胀导致的过分调度问题。

线程池可以看作是线程的集合,通过使用线程池,我们可以:

  1. 重用已创建的线程:避免因为线程创建和销毁所带来的性能开销
  2. 控制并发的线程数量:防止因为创建大量线程而导致系统资源耗尽
  3. 管理线程的生命周期:可以对线程进行统一的分配、调优和监控

1.2 为什么需要线程池

在并发编程中,多线程技术已经成为提高系统性能的重要手段。但直接创建线程存在以下问题:

  1. 频繁创建和销毁线程带来的系统开销大:线程的创建和销毁需要时间,如果任务执行时间很短,频繁创建销毁线程会使得性能下降
  2. 线程无限制创建,会导致系统资源耗尽:线程是有限的系统资源,如果无限制创建,可能会导致内存溢出
  3. 线程创建和管理缺乏统一的规划:不利于系统的维护和扩展

下面是一个对比示例,展示直接创建线程和使用线程池的区别:

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

public class ThreadPoolDemo {
   
    
    // 执行任务的数量
    private static final int TASK_COUNT = 10000;
    
    // 使用直接创建线程的方式执行任务
    public static void executeWithDirectThreads() {
   
        long start = System.currentTimeMillis();
        
        for (int i = 0; i < TASK_COUNT; i++) {
   
            Thread thread = new Thread(() -> {
   
                // 模拟任务执行
                try {
   
                    Thread.sleep(10);
                } catch (InterruptedException e) {
   
                    Thread.currentThread().interrupt();
                }
            });
            thread.start();
        }
        
        long end = System.currentTimeMillis();
        System.out.println("直接创建线程耗时: " + (end - start) + "ms");
    }
    
    // 使用线程池执行任务
    public static void executeWithThreadPool() {
   
        ExecutorService executor = Executors.newFixedThreadPool(10);
        long start = System.currentTimeMillis();
        
        for (int i = 0; i < TASK_COUNT; i++) {
   
            executor.execute(() -> {
   
                // 模拟任务执行
                try {
   
                    Thread.sleep(10);
                } catch (InterruptedException e) {
   
                    Thread.currentThread().interrupt();
                }
            });
        }
        
        executor.shutdown();
        try {
   
            // 等待所有任务完成
            executor.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
   
            Thread.currentThread().interrupt();
        }
        
        long end = System.currentTimeMillis();
        System.out.println("使用线程池耗时: " + (end - start) + "ms");
    }
    
    public static void main(String[] args) {
   
        executeWithDirectThreads();
        executeWithThreadPool();
    }
}

运行上面的代码,可以明显看到使用线程池执行任务比直接创建线程要快得多,并且资源利用更加合理。

1.3 线程池的核心参数

Java中的线程池主要由ThreadPoolExecutor类实现,它提供了一系列参数来配置线程池的行为:

public ThreadPoolExecutor(
    int corePoolSize,                 // 核心线程数
    int maximumPoolSize,              // 最大线程数
    long keepAliveTime,               // 线程存活时间
    TimeUnit unit,                    // 时间单位
    BlockingQueue<Runnable> workQueue, // 工作队列
    ThreadFactory threadFactory,       // 线程工厂
    RejectedExecutionHandler handler   // 拒绝策略
)

各参数详细介绍:

  1. corePoolSize(核心线程数):线程池中的基本线程数量,即使线程空闲,也会保持在池中,除非设置了allowCoreThreadTimeOut
  2. maximumPoolSize(最大线程数):线程池允许创建的最大线程数
  3. keepAliveTime(线程存活时间):当线程数大于核心线程数时,多余的空闲线程在终止前等待新任务的最长时间
  4. unit(时间单位)keepAliveTime参数的时间单位,可以是毫秒、秒、分钟等
  5. workQueue(工作队列):用于保存等待执行的任务的阻塞队列
  6. threadFactory(线程工厂):用于创建新线程的工厂
  7. handler(拒绝策略):当队列和线程池都满了,新任务的处理策略

1.4 线程池工作原理

线程池的工作原理如下图所示:

   ┌─────────────────────┐       ┌─────────────────────┐
   │                     │       │                     │
   │                     │       │                     │
   │      任务提交       │       │       任务执行      │
   │                     │       │                     │
   │                     │       │                     │
   └─────────┬───────────┘       └─────────▲───────────┘
             │                             │
             │                             │
             │                             │
             ▼                             │
   ┌─────────────────────┐       ┌────────┴────────┐
   │                     │  yes  │                 │
   │ 核心线程池是否已满? ├──────►│  创建新的线程   │
   │                     │       │                 │
   └─────────┬───────────┘       └─────────────────┘
             │ no
             │
             ▼
   ┌─────────────────────┐       ┌─────────────────────┐
   │                     │  yes  │                     │
   │   工作队列是否已满? ├──────►│最大线程池是否已满?  │
   │                     │       │                     │
   └─────────┬───────────┘       └─────────┬───────────┘
             │ no                          │
             │                             │ no
             ▼                             ▼
   ┌─────────────────────┐       ┌─────────────────────┐
   │                     │       │                     │
   │  将任务加入工作队列 │       │    创建新的线程     │
   │                     │       │                     │
   └─────────────────────┘       └─────────────────────┘
                                            │
                                            │ yes
                                            ▼
                                 ┌─────────────────────┐
                                 │                     │
                                 │    执行拒绝策略     │
                                 │                     │
                                 └─────────────────────┘

线程池的处理流程:

  1. 当提交一个新任务到线程池时,线程池会先检查核心线程池是否已满
  2. 如果核心线程池未满,则创建一个新的工作线程来执行任务
  3. 如果核心线程池已满,则将任务添加到工作队列中
  4. 如果工作队列已满,则线程池会检查最大线程池是否已满
  5. 如果最大线程池未满,则创建一个新的工作线程来执行任务
  6. 如果最大线程池已满,则根据拒绝策略来处理该任务

下面通过一个简单的示例来演示线程池的处理流程:

import java.util.concurrent.*;

public class ThreadPoolWorkingDemo {
   
    public static void main(String[] args) {
   
        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,                                   // 核心线程数
            4,                                   // 最大线程数
            60, TimeUnit.SECONDS,                // 线程存活时间
            new ArrayBlockingQueue<>(2),         // 工作队列容量为2
            Executors.defaultThreadFactory(),    // 默认线程工厂
            new ThreadPoolExecutor.CallerRunsPolicy() // 使用调用者运行策略
        );
        
        // 模拟10个任务提交到线程池
        for (int i = 1; i <= 10; i++) {
   
            final int taskId = i;
            System.out.println("提交任务: " + taskId);
            
            executor.execute(() -> {
   
                System.out.println("开始执行任务: " + taskId + 
                                 ", 线程名: " + Thread.currentThread().getName());
                try {
   
                    // 模拟任务执行耗时
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
                    Thread.currentThread().interrupt();
                }
                System.out.println("完成任务: " + taskId + 
                                 ", 线程名: " + Thread.currentThread().getName());
            });
            
            System.out.println("线程池状态: " + 
                             "核心线程数: " + executor.getCorePoolSize() +
                             ", 活动线程数: " + executor.getActiveCount() +
                             ", 最大线程数: " + executor.getMaximumPoolSize() +
                             ", 线程池大小: " + executor.getPoolSize() +
                             ", 队列任务数: " + executor.getQueue().size() +
                             ", 已完成任务数: " + executor.getCompletedTaskCount());
        }
        
        // 关闭线程池
        executor.shutdown();
    }
}

2. Java中的线程池类型与使用

2.1 常见的线程池类型

Java通过Executors工厂类提供了几种常见的线程池实现,每种线程池都有其特定的使用场景:

2.1.1 FixedThreadPool

FixedThreadPool是一个固定大小的线程池,它的特点是:

  • 核心线程数和最大线程数相同
  • 使用无界队列LinkedBlockingQueue
  • 线程会一直存活,不会被回收
  • 适合处理长期存在的任务,负载较重的服务器
// 创建固定大小为5的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

内部实现:

public static ExecutorService newFixedThreadPool(int nThreads) {
   
    return new ThreadPoolExecutor(
        nThreads,           // 核心线程数
        nThreads,           // 最大线程数(与核心线程数相同)
        0L,                 // 线程存活时间(不会回收)
        TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>() // 无界队列
    );
}
2.1.2 CachedThreadPool

CachedThreadPool是一个会根据需要创建新线程的线程池,它的特点是:

  • 核心线程数为0,最大线程数为Integer.MAX_VALUE
  • 使用SynchronousQueue作为工作队列
  • 空闲线程会在60秒后被回收
  • 适合执行大量短期异步任务的程序
// 创建缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

内部实现:

public static ExecutorService newCachedThreadPool() {
   
    return new ThreadPoolExecutor(
        0,                       // 核心线程数为0
        Integer.MAX_VALUE,       // 最大线程数非常大
        60L,                     // 空闲线程存活时间
        TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>() // 同步队列
    );
}
2.1.3 SingleThreadExecutor

SingleThreadExecutor是一个只有一个工作线程的线程池,它的特点是:

  • 核心线程数和最大线程数都为1
  • 使用无界队列LinkedBlockingQueue
  • 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
  • 适合需要保证顺序执行各个任务的应用场景
// 创建单线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

内部实现:

public static ExecutorService newSingleThreadExecutor() {
   
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(
            1,                      // 核心线程数为1
            1,                      // 最大线程数也为1
            0L,                     // 线程存活时间(不会回收)
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>() // 无界队列
        ));
}
2.1.4 ScheduledThreadPool

ScheduledThreadPool是一个可以定时执行任务的线程池,它的特点是:

  • 核心线程数固定,最大线程数为Integer.MAX_VALUE
  • 使用DelayedWorkQueue作为工作队列
  • 可以执行定时任务或周期性任务
  • 适合需要多个后台线程执行周期任务的场景
// 创建具有3个核心线程的ScheduledThreadPool
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);

内部实现:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
   
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

// ScheduledThreadPoolExecutor的构造函数
public ScheduledThreadPoolExecutor(int corePoolSize) {
   
    super(
        corePoolSize,             // 指定核心线程数
        Integer.MAX_VALUE,        // 最大线程数非常大
        0,                        // 线程存活时间
        TimeUnit.NANOSECONDS,
        new DelayedWorkQueue()    // 延迟工作队列
    );
}

2.2 线程池的使用示例

2.2.1 FixedThreadPool的使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class FixedThreadPoolExample {
   
    public static void main(String[] args) {
   
        // 创建固定大小为3的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 提交10个任务给线程池执行
        for (int i = 1; i <= 10; i++) {
   
            final int taskId = i;
            executor.

你可能感兴趣的:(java,spring,boot)