当前位置: 首页 > news >正文

java教程笔记(十四)-线程池

Java 的线程池(ThreadPool)是并发编程中用于管理和复用线程的重要机制。它通过预先创建一组线程并重复使用它们来执行多个任务,从而减少频繁创建和销毁线程的开销,提高程序性能。

1.线程池的核心概念

1. 线程池的作用

  • 降低资源消耗:避免频繁创建/销毁线程。
  • 提高响应速度:任务到达后可以直接使用已有线程执行。
  • 提高线程可管理性:统一管理线程生命周期、限制最大并发数、防止资源耗尽。

2.Java 中线程池的实现类:ThreadPoolExecutor

ThreadPoolExecutor 是 Java 标准库中最核心的线程池实现类,构造函数如下:

推荐通过构造函数配置参数自定义创建线程池 

ThreadPoolExecutor(
int corePoolSize, 
int maximumPoolSize,long keepAliveTime, 
TimeUnit unit, 
BlockingQueue<Runnable> workQueue, 
ThreadFactory threadFactory, 
RejectedExecutionHandler handler)

参数说明:

参数名描述
corePoolSize核心线程数(常驻线程数)
maximumPoolSize最大线程数
keepAliveTime非核心线程空闲超时时间
unit超时单位
workQueue任务队列
threadFactory线程工厂,用于创建新线程
handler拒绝策略

3.线程池的工作流程

  1. 提交任务时:

    • 如果当前线程数 < corePoolSize,创建新线程处理任务。
    • 如果当前线程数 >= corePoolSize,将任务放入队列。
    • 如果队列已满且当前线程数 < maximumPoolSize,创建新线程处理任务。
    • 如果队列已满且线程数 >= maximumPoolSize,执行拒绝策略。
  2. 空闲线程会根据 keepAliveTime 判断是否回收(非核心线程优先回收)。

4.常见的线程池类型(来自 Executors 工具类)

Java 提供了几个常用使用 Executors 工具类创建线程池的方法

线程池类型特点使用场景
newFixedThreadPool

核心线程数 = 最大线程数,所有线程都是核心线程

线程不会超时回收,会一直存活

使用无界队列 LinkedBlockingQueue

适用于负载较重、任务量稳定的系统
newCachedThreadPool

核心线程数为 0,最大线程数为 Integer.MAX_VALUE

空闲线程超时时间为 60 秒,超过时间会被回收

使用同步队列 SynchronousQueue

适用于执行大量短期异步任务
newSingleThreadExecutor

只有一个线程工作的线程池

所有任务按顺序执行(保证串行)

使用无界队列 LinkedBlockingQueue

保证任务串行执行,避免并发问题
newScheduledThreadPool

支持定时和周期性任务调度

使用延迟队列 DelayedWorkQueue

可以提交 Runnable 或 Callable 任务,并指定延迟或周期执行

定时调度、延迟执行等场景
newWorkStealingPool (JDK8+)使用 ForkJoinPool 实现,支持工作窃取算法大规模并行任务调度

示例代码:

ExecutorService executor = Executors.newFixedThreadPool(5); 
executor.submit(() -> System.out.println("Task executed"));

5.拒绝策略(RejectedExecutionHandler)

当线程池无法处理新任务时,会调用拒绝策略。常见的内置策略有:

策略行为
AbortPolicy(默认)抛出 RejectedExecutionException 异常
CallerRunsPolicy由提交任务的线程自己执行任务
DiscardPolicy直接丢弃任务,不抛异常
DiscardOldestPolicy丢弃队列中最老的任务,尝试重新提交当前任务

自定义拒绝策略:

RejectedExecutionHandler handler = (r, executor) -> { System.out.println("Task rejected: " + r.toString()); };

6.创建线程池之后,如何使用 

线程池创建之后,主要通过提交任务来使用它。Java 中的线程池(ExecutorService)提供了多种方式来提交任务,并支持同步、异步、定时等执行模式。

1.提交任务的方式

1. execute(Runnable command)

  • 用途:提交一个不需要返回结果的任务。
  • 特点
    • 异步执行
    • 不返回结果
    • 不抛出异常(需手动捕获)
ExecutorService executor = Executors.newFixedThreadPool(4);executor.execute(() -> { System.out.println("Task executed in thread: " + Thread.currentThread().getName()); });

2. submit(Runnable task)

  • 用途:提交一个没有返回值的任务,返回一个 Future 对象。
  • 特点
    • 可用于跟踪任务是否完成
    • 调用 future.get() 可以阻塞等待任务结束
Future<?> future = executor.submit(() -> { System.out.println("Running task..."); }); future.get(); // 阻塞直到任务完成

3. submit(Callable<T> task)

  • 用途:提交一个有返回值的任务。
  • 特点
    • 返回泛型结果
    • 支持异常抛出
    • 使用 Future.get() 获取结果或捕获异常
Future<Integer> future = executor.submit(() -> { return 42; });System.out.println("Result: " + future.get()); // 输出 42

4. invokeAll(Collection<? extends Callable<T>> tasks)

  • 用途:批量提交多个 Callable 任务,等待所有任务完成。
  • 特点
    • 返回 List<Future<T>>
    • 可设置超时时间
List<Callable<String>> tasks = Arrays.asList( () -> "Task 1", () -> "Task 2", () -> "Task 3" ); 
List<Future<String>> results = executor.invokeAll(tasks); 
for (Future<String> result : results) { System.out.println(result.get()); }

5. invokeAny(Collection<? extends Callable<T>> tasks)

  • 用途:提交多个任务,只要有一个任务完成就返回其结果,其他任务取消。
  • 特点
    • 提升响应速度
    • 常用于“最先完成者胜出”的场景
String result = executor.invokeAny(tasks); // 返回第一个完成的结果 System.out.println("First completed: " + result)

2.定时任务调度(适用于 ScheduledExecutorService

如果使用的是 newScheduledThreadPool,可以使用以下方法:

1. schedule(Runnable command, long delay, TimeUnit unit)

  • 用途:延迟执行一次任务
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); 
scheduler.schedule(() -> System.out.println("Delayed task"), 3, TimeUnit.SECONDS);

2. scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

  • 用途:固定频率执行任务(即使上一次任务未完成,也会在周期到达时启动下一次)
scheduler.scheduleAtFixedRate(() -> { System.out.println("Fixed rate task"); }, 0, 1, TimeUnit.SECONDS);

3. scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

  • 用途:固定延迟执行任务(每次任务完成后,再等待指定时间)
scheduler.scheduleWithFixedDelay(() -> { System.out.println("Fixed delay task"); }, 0, 1, TimeUnit.SECONDS);

3.关闭线程池

线程池使用完毕后必须关闭,否则可能导致程序无法正常退出。

1. shutdown()

  • 作用:不再接受新任务,但会等待已提交任务执行完毕。

executor.shutdown();

2. shutdownNow()

  • 作用:尝试立即停止所有正在执行的任务,并返回尚未执行的任务列表。

List<Runnable> pendingTasks = executor.shutdownNow();

3. 等待关闭完成(可选)

if (!executor.isShutdown()) { 
executor.shutdown(); 
} 
try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { 
executor.shutdownNow();} 
} 
catch (InterruptedException e) { executor.shutdownNow(); }

4.完整示例

import java.util.concurrent.*;public class ThreadPoolUsage {public static void main(String[] args) throws Exception { 
ExecutorService executor = Executors.newFixedThreadPool(4); 
// 提交 Runnable 任务executor.execute(() -> System.out.println("Hello from execute")); 
// 提交 Callable 任务并获取结果 
Future<Integer> future = executor.submit(() -> { return 100; }); System.out.println("Callable result: " + future.get()); // 定时任务 ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); scheduler.schedule(() -> System.out.println("Delayed task"), 2, TimeUnit.SECONDS); 
// 关闭线程池 
executor.shutdown(); 
scheduler.shutdown();} 
}

7.总结

操作方法说明
提交无返回任务execute(Runnable)最简单的异步执行方式
提交无返回任务submit(Runnable)返回 Future 可判断是否完成
提交有返回任务submit(Callable)支持返回值和异常处理
批量提交任务invokeAll()返回所有任务的 Future 列表
任一任务完成即返回invokeAny()适用于多任务中取最快结果
定时/周期任务schedule*()适用于 ScheduledExecutorService
关闭线程池shutdown() / shutdownNow()必须显式调用

合理使用这些方法,可以充分发挥线程池的优势,提高并发性能与资源利用率。

8.线程池的监控与关闭

1. 常用监控方法:

方法说明
getPoolSize()获取当前线程池中的线程数量
getActiveCount()获取正在执行任务的线程数
getCompletedTaskCount()获取已完成的任务数
getQueue()获取等待执行的任务队列
boolean isShutdown()判断线程池是否已关闭
boolean isTerminated()判断线程池是否完全终止(所有任务执行完并关闭)
boolean awaitTermination(long timeout, TimeUnit unit)等待线程池终止

9.最佳实践

  • 合理设置核心线程数和最大线程数,避免资源浪费或线程爆炸。
  • 选择合适的任务队列(如 LinkedBlockingQueueArrayBlockingQueue)。
  • 避免任务堆积,设置合理的拒绝策略。
  • 及时关闭线程池,防止内存泄漏。
  • 使用 Future 或 CompletableFuture 获取任务结果或异常信息

http://www.xdnf.cn/news/955747.html

相关文章:

  • DAY 44 训练
  • RK3588开发笔记-wifi6 SDIO接口rtl8822cs调试笔记
  • Mysql故障排插与环境优化
  • ESP32-S3 IDF V5.4.1 LVGL 9.2.0 fatfs
  • RabbitMQ优先级队列的使用
  • 掌握业务三板斧:目标、过程、成果
  • 视频监控管理平台EasyCVR接入RTMP推流后FLV格式流高延时问题解决方案
  • 会计 - 股份支付
  • 六、接口关联
  • 对云边协同技术的理解
  • Python 协程全解析:async/await、asyncio.run、协程 vs 多线程、I/O 密集首选协程
  • EasyExcel读取csv文件乱码
  • 在 JavaScript中编写 Appium 测试(入门)
  • 【后端】单点登录
  • BI系统帮助企业释放数据价值
  • 技术突破与落地应用:端到端 2.0 时代辅助驾驶TOP10 论文深度拆解系列【第二篇(排名不分先后)】
  • Zabbix 高可用架构部署方案(2最新版)
  • DAY 45 超大力王爱学Python
  • JAVA实战开源项目:经方药食两用服务平台 (Vue+SpringBoot) 附源码
  • day030-Shell自动化编程-函数
  • Ubuntu Linux环境查看服务器资源,查询CPU,内存,环境变量等命令
  • 标注工具核心代码解析——load_image【canvas.py]
  • NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
  • 深度解析:DDoS攻击及其防御策略
  • 【深度解析】以太坊中的挖矿难度调整机制:从动态调节到“冰河时代”的倒计时
  • FOPLP vs CoWoS
  • 二、ROS2完成Docker容器和宿主机通信,使用ros2 topic list看到,但是无法echo
  • 驭码 CodeRider 2.0 产品体验:智能研发的革新之旅
  • OceanBase 桌面版
  • 从零开始了解数据采集(二十八)——制造业数字孪生