Java并发编程实战-多线程任务执行

Executor框架与线程池(ThreadPoolExecutor)

Executor框架的组成

组件 作用
Executor 基础接口,仅定义execute(Runnable)方法,用于执行任务。
ExecutorService 扩展Executor,提供任务提交(submit)、线程池关闭(shutdown)等功能。
ScheduledExecutorService 支持定时或周期性任务调度。
ThreadPoolExecutor 最常用的线程池实现类,提供高度可配置的线程池管理。
Executors 工厂类,提供创建常见线程池的快捷方法(如newFixedThreadPool)。

ThreadPoolExecutor的核心机制

  • 线程池的构造参数

    arduino 复制代码
    public ThreadPoolExecutor(
        int corePoolSize,          // 核心线程数
        int maximumPoolSize,       // 最大线程数
        long keepAliveTime,        // 空闲线程存活时间
        TimeUnit unit,             // 时间单位
        BlockingQueue<Runnable> workQueue,  // 任务队列
        ThreadFactory threadFactory,        // 线程工厂
        RejectedExecutionHandler handler    // 拒绝策略
    )
  • 任务提交

    • 若当前线程数 < corePoolSize,立即创建新线程执行任务。
    • 若线程数 ≥ corePoolSize,将任务放入workQueue等待。
    • 若队列已满且线程数 < maximumPoolSize,创建临时线程处理任务。
    • 若队列已满且线程数 ≥ maximumPoolSize,触发RejectedExecutionHandler拒绝任务。
  • 线程回收

    • 临时线程(超过corePoolSize的部分)在空闲keepAliveTime后被回收。
    • 核心线程默认不回收,可通过allowCoreThreadTimeOut(true)启用回收。
  • 关键参数详解

    参数 作用与典型值
    corePoolSize 核心线程数(常驻线程),默认长期存活。CPU密集型任务建议设为CPU核心数+1
    maximumPoolSize 最大线程数(含核心线程)。IO密集型任务可设较高值(如2*CPU核心数)。
    keepAliveTime 临时线程空闲存活时间(如30秒)。
    workQueue 任务队列,具体实现: LinkedBlockingQueue:无界队列(需防OOM) ArrayBlockingQueue:有界队列(需合理容量) SynchronousQueue:直接传递队列(高吞吐)
    threadFactory 自定义线程创建逻辑(如命名线程、设置优先级)。
    handler 拒绝策略(当队列和线程池全满时处理新任务): AbortPolicy(默认):抛出RejectedExecutionException CallerRunsPolicy:由提交任务的线程执行任务 DiscardPolicy:静默丢弃任务 DiscardOldestPolicy:丢弃队列最旧任务并重试提交
  • CorePoolSize设置

    • CPU密集型任务(如计算、压缩):

      • 核心线程数 = CPU核心数 + 1
      • 公式:corePoolSize = N + 1(N为CPU核心数),避免线程竞争导致的上下文切换开销。
    • I/O密集型任务(如网络请求、数据库查询):

      • 核心线程数 = CPU核心数 × 2
      • 公式:corePoolSize = N × (1 + 平均I/O等待时间 / 平均计算时间),若无法精确统计,可设为 2N~5N

线程池的创建与使用

方法 实现类 特性
newFixedThreadPool(n) ThreadPoolExecutor 固定大小线程池,无界队列(LinkedBlockingQueue),核心线程数=最大线程数。
newCachedThreadPool() ThreadPoolExecutor 可扩容线程池,最大线程数为Integer.MAX_VALUE,使用SynchronousQueue
newSingleThreadExecutor() ThreadPoolExecutor 单线程池,任务按顺序执行,无界队列。
newScheduledThreadPool(n) ScheduledThreadPoolExecutor 支持定时任务调度。

任务分界与执行策略

任务分界的原则

  • 独立性

    • 无共享状态:任务之间尽量不依赖共享数据,避免同步开销。
    • 示例:将大数组分割为多个子数组,分别计算子数组的和。
  • 粒度平衡

    • 粗粒度:任务过少,无法充分利用多核资源。
    • 细粒度:任务过多,线程管理开销增大。
    • 目标:根据硬件(如CPU核心数)和任务类型(CPU/IO密集型)调整任务粒度。
  • 可组合性 :支持将子任务结果合并为最终结果(如ForkJoinPoolRecursiveTask)。

任务分界的方法

  • 数据分片(Data Partitioning)

    • 原理:将数据集划分为多个子集,每个线程处理一个子集。
    • 适用场景:批量数据处理(如统计、图像渲染)。
  • 任务流水线(Pipeline)

    • 原理:将任务拆分为多个阶段,每个阶段由独立线程处理(类似工厂流水线)。
    • 适用场景:多阶段处理(如日志采集 → 清洗 → 存储)。
  • 递归分解(Recursive Decomposition)

    • 原理:将任务递归拆分为更小的子任务,直到达到阈值后顺序执行。
    • 适用场景:分治算法(如归并排序、快速排序)。

执行策略的类型与选择

  • 并行处理(Parallel Processing)

    • 核心思想:将任务分配给多个线程同时执行,充分利用多核CPU。
    • 适用场景:计算密集型任务(如数值计算、图像处理)。
  • 异步执行(Asynchronous Execution)

    • 核心思想 :任务提交后立即返回,通过回调或Future获取结果,不阻塞主线程。
    • 适用场景:IO密集型任务(如网络请求、文件读写)。
    • 实现工具CompletableFuture(链式异步编程)、Future + ExecutorService
  • 混合策略

    • 接收请求:使用异步IO(如NIO)非阻塞接收连接。
    • 处理请求:CPU密集型任务交给固定线程池,IO操作使用异步回调。

执行策略的调优技巧

  • 线程池配置

    • 计算密集型:线程数 ≈ CPU核心数(避免过多线程竞争CPU)。
    • IO密集型:线程数 ≈ CPU核心数 × (1 + 平均IO等待时间/计算时间)。
    • 动态调整:根据监控指标(队列长度、活跃线程数)调整线程池参数。
  • 任务队列选择

    • 无界队列 (如LinkedBlockingQueue):适合任务量不可控但需快速响应。
    • 有界队列 (如ArrayBlockingQueue):避免内存溢出,需配合拒绝策略。
    • 直接传递队列SynchronousQueue):高吞吐,要求线程池足够大。
  • 拒绝策略

    • AbortPolicy:直接抛出异常,默认策略。
    • CallerRunsPolicy:由提交任务的线程执行任务,降低提交速率。
    • DiscardOldestPolicy:丢弃队列中最旧的任务,腾出空间。
    • 自定义策略:记录日志或持久化被拒绝的任务。

任务取消的机制

Future.cancel()方法定义

arduino 复制代码
boolean cancel(boolean mayInterruptIfRunning);
  • 作用:尝试取消任务的执行。

  • 参数mayInterruptIfRunning表示是否允许中断正在执行任务的线程。

  • 返回值

    • true:任务成功取消(任务未启动或已启动且允许中断)。
    • false:任务已结束或无法取消。

cancel()的行为场景

  • 任务尚未启动:任务直接从任务队列中移除,不会执行。

  • 任务正在运行 :根据**mayInterruptIfRunning**参数决定是否中断任务线程:

    • mayInterruptIfRunning=true:向任务线程发送中断信号(设置中断标志位)。
    • mayInterruptIfRunning=false:不中断线程,任务继续执行,但若任务未开始则取消。
  • 任务已完成或已取消 :调用cancel()返回false,无实际效果。

任务如何响应取消

  • 协作式中断 :任务的取消需要协作,即任务必须主动检查中断状态或处理中断异常。

    • 检查中断标志 :在循环或耗时操作中定期调用Thread.interrupted()
    • 处理InterruptedException :在阻塞操作(如Thread.sleep()wait())中捕获异常并退出。

与其他机制对比

机制 原理 优点 缺点
Future.cancel() 基于中断标志的协作式取消 标准化,与线程池集成方便 依赖任务协作,无法强制终止
自定义标志位 通过volatile boolean控制 灵活,无中断依赖 需手动检查,无法中断阻塞操作
Thread.stop() 强制终止线程(已废弃) 立即停止任务 不安全,易导致数据不一致
相关推荐
你们补药再卷啦1 小时前
springboot 项目 jmeter简单测试流程
java·spring boot·后端
网安密谈1 小时前
SM算法核心技术解析与工程实践指南
后端
bobz9651 小时前
Keepalived 检查和通知脚本
后端
AKAMAI1 小时前
教程:在Linode平台上用TrueNAS搭建大规模存储系统
后端·云原生·云计算
盘盘宝藏1 小时前
idea搭建Python环境
后端·intellij idea
喵手1 小时前
Spring Boot 项目基于责任链模式实现复杂接口的解耦和动态编排!
spring boot·后端·责任链模式
大鹏dapeng1 小时前
使用gone v2 的 Provider 机制升级改造 goner/xorm 的过程记录
后端·设计模式·go
雷渊1 小时前
介绍一下RocketMQ的几种集群模式
java·后端·面试
讳疾忌医_note1 小时前
别再错用 C++ 线程池!正确姿势与常见误区大揭秘
后端
快乐源泉1 小时前
【设计模式】参数校验逻辑复杂,代码长?用责任链
后端·设计模式·go