深入浅出Java线程池

深入浅出Java线程池

什么是线程池?

想象你开了一家快递站,每天有很多包裹要派送。如果每来一个包裹就雇一个新快递员,送完就解雇,这样效率很低,因为:

  1. 频繁招聘和解雇成本高(对应线程创建和销毁开销大)
  2. 新快递员不熟悉路线(线程需要时间初始化)
  3. 快递员太多时管理混乱(系统资源耗尽)

线程池就像你预先雇佣的一批固定快递员(线程),有包裹(任务)来了就分配给他们,送完继续等待新任务,这样效率更高。

为什么需要线程池?

  1. 降低资源消耗:重复利用已创建的线程,避免频繁创建销毁
  2. 提高响应速度:任务到达时直接使用现有线程,无需等待线程创建
  3. 便于管理:可以统一分配、监控和调优线程资源

Java线程池核心类

Java中的线程池主要通过java.util.concurrent包中的ExecutorService接口及其实现类ThreadPoolExecutor来实现。

线程池工作原理

线程池就像一个有管理的"线程工厂+任务队列":

  1. 核心线程:池中常驻的基本劳动力,即使空闲也不销毁
  2. 任务队列:当核心线程都忙时,新任务进入队列等待
  3. 非核心线程:当队列满了,创建额外线程帮忙(有数量限制)
  4. 拒绝策略:当线程和队列都满了,如何处理新任务

创建线程池的常用方法

Java提供了Executors工厂类来创建常见类型的线程池:

java 复制代码
// 1. 固定大小线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);

// 2. 单线程池(保证任务顺序执行)
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

// 3. 可缓存线程池(自动扩容缩容)
ExecutorService cachedPool = Executors.newCachedThreadPool();

// 4. 定时任务线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);

更灵活的ThreadPoolExecutor

实际上,上述工厂方法内部都是使用ThreadPoolExecutor构造的。直接使用它可更精细控制:

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5,                      // 核心线程数
    10,                     // 最大线程数
    60,                     // 空闲线程存活时间(秒)
    TimeUnit.SECONDS,       // 时间单位
    new ArrayBlockingQueue<>(100), // 任务队列
    Executors.defaultThreadFactory(), // 线程工厂
    new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

线程池重要参数

  1. corePoolSize:核心线程数,池中常驻线程数量
  2. maximumPoolSize:最大线程数,池中允许的最大线程数
  3. keepAliveTime:非核心线程空闲时的存活时间
  4. workQueue:任务队列,保存等待执行的任务
  5. threadFactory:创建线程的工厂
  6. handler:拒绝策略,当线程和队列都满时的处理方式

四种拒绝策略

  1. AbortPolicy(默认):直接抛出RejectedExecutionException异常
  2. CallerRunsPolicy:让提交任务的线程自己执行该任务
  3. DiscardPolicy:默默丢弃无法处理的任务
  4. DiscardOldestPolicy:丢弃队列中最老的任务,然后重试提交

使用示例

java 复制代码
public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);
        
        // 提交10个任务
        for (int i = 1; i <= 10; i++) {
            final int taskId = i;
            pool.execute(() -> {
                System.out.println("任务" + taskId + "正在执行,线程:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模拟任务耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务" + taskId + "执行完毕");
            });
        }
        
        // 关闭线程池
        pool.shutdown();
    }
}

线程池生命周期

  1. RUNNING:接受新任务,处理队列任务
  2. SHUTDOWN:不接受新任务,但处理队列中的任务
  3. STOP:不接受新任务,不处理队列任务,中断正在执行的任务
  4. TIDYING:所有任务终止,workerCount为0
  5. TERMINATED:terminated()方法执行完毕

最佳实践

  1. 根据任务类型选择合适线程池:

    • CPU密集型:线程数 ≈ CPU核心数
    • IO密集型:线程数可以多一些(如CPU核心数×2)
  2. 避免使用无界队列,可能导致内存溢出

  3. 合理设置线程存活时间

  4. 为线程池中的线程设置有意义的名字,便于排查问题

  5. 考虑使用自定义的ThreadFactory,设置线程优先级、异常处理等

线程池是Java并发编程的重要工具,合理使用可以显著提高程序性能,但需要根据实际场景仔细配置参数。

相关推荐
猴哥源码几秒前
基于Java+SpringBoot的健身房管理系统
java·spring boot
极光雨雨5 分钟前
Spring Bean 控制销毁顺序的方法总结
java·spring
猴哥源码9 分钟前
基于Java+SpringBoot的三国之家网站
java·spring boot
念九_ysl23 分钟前
Java 使用 OpenHTMLToPDF + Batik 将含 SVG 遮罩的 HTML 转为 PDF 的完整实践
java·开发语言·pdf
yaoxin52112333 分钟前
124. Java 泛型 - 有界类型参数
java·开发语言
Spirit_NKlaus36 分钟前
解决HttpServletRequest无法获取@RequestBody修饰的参数
java·spring boot·spring
不死的精灵42 分钟前
【Java21】在spring boot中使用ScopedValue
java·spring boot·后端
tmacfrank44 分钟前
Android 网络全栈攻略(四)—— TCPIP 协议族与 HTTPS 协议
android·网络·https
勤奋的知更鸟1 小时前
Java 编程之模板方法模式
java·开发语言·模板方法模式
逸风尊者1 小时前
开发易掌握的知识:GeoHash查找附近空闲车辆
java·后端