并发编程:ScheduledThreadPoolExecutor你真的了解吗?

前言

首先看到标题,我们其实很多人都知道,但是呢 在实际项目中我们面对很多延迟任务实现方案有很多选择,甚至直接在网上百度反正都能实现就行,但是忽略了很多细节,导致生产上的事故,都是因为没有真正了解到底层的运行,当然这篇博文不去说源码而是说实战,源码的尽头都是线程池,这个已经在之前已经详细介绍过了

ScheduledThreadPoolExecutor和@Scheduled的关系

说到定时任务大家首先想到的是@Scheduled注解,其实这个注解的底层就是ScheduledThreadPoolExecutor
在Spring Boot中集成ScheduledThreadPoolExecutor用于执行定时任务,你可以通过以下步骤来实现:

定义线程池配置类

创建一个配置类来定义你的ScheduledThreadPoolExecutor实例。这里是一个简单的示例:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;@Configuration
public class ThreadPoolConfig {@Beanpublic ThreadPoolTaskScheduler taskScheduler() {ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();scheduler.setPoolSize(10); // 设置线程池大小scheduler.setThreadNamePrefix("scheduled-task-"); // 设置线程前缀scheduler.setRemoveOnCancelPolicy(true); // 取消任务时是否移除return scheduler;}
}

注意,虽然上述示例中使用了ThreadPoolTaskScheduler,它是Spring对定时任务线程池的封装,底层也是基于ScheduledThreadPoolExecutor实现的,因此适合用于集成定时任务。

使用@EnableScheduling开启定时任务支持

在你的Spring Boot主类或配置类上添加@EnableScheduling注解,以启用定时任务功能。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication
@EnableScheduling
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

编写定时任务方法

在需要执行定时任务的类上使用@Component注解使其成为Spring管理的Bean,并在相应的方法上使用@Scheduled注解来定义任务的执行规则。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
public class MyScheduledTasks {@Autowiredprivate SomeService someService;@Scheduled(fixedRate = 60000) // 每60秒执行一次public void scheduledTask() {someService.doSomething();}
}

在这个例子中,fixedRate = 60000表示方法执行的间隔时间,单位是毫秒,这里设置为每60秒执行一次。

通过上述步骤,你就可以在Spring Boot应用中集成并使用ScheduledThreadPoolExecutor来执行定时任务了。记得根据实际需求调整线程池的大小和其他参数。

切记spring中几乎所有关于定时任务线程池的封装都是通过ScheduledThreadPoolExecutor来执行的
另外这个有个陷阱那就是一定需要自定义线程池的配置类,为什么呢,看看@Scheduled引发的生产事故

@Scheduled引发的生产事故

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面的截图只是一个demo,其中一个定时任务卡死了,导致其他的定时任务无法启动,从而造成了线上故障,而这些定时器任务都是由ThreadPoolTaskScheduleduler来调度的,这个这个线程池的核心线程是1,所以一定要自定义配置类,重新设置核心线程数
在这里插入图片描述
或者
在这里插入图片描述

ScheduledThreadPoolExecutor和@Scheduled能解决什么业务场景

从刚刚上面的例子无论是ThreadPoolTaskScheduler还是@Scheduled其实底层都是ScheduledThreadPoolExecutor进行封装的,那么接下来
我们看下这个如何使用呢?

scheduledExecutorService.scheduled
等待执行时间之后,运行任务。(任务,延迟时间,延迟时间单位)
ScheduledExecutorService.schedule(Callable callable, long delay, TimeUnit unit)

主程序

创建5个任务,分别延迟1 2 3 4 5 秒执行,然后等待任务完成。

package xyz.jangle.thread.test.n4_6.schedule;import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;/*** 4.6、schedule 在执行器内延迟执行任务* 	创建5个任务,分别延迟1 2 3 4 5 秒执行,然后等待任务完成。* * 	注:如果希望在指定时间执行任务, 则计算当前到指定时间的间隔。* @author jangle* @email jangle@jangle.xyz* @time 2020年8月21日 下午7:00:39* */
public class M {public static void main(String[] args) {ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
/*
//		ScheduledThreadPoolExecutor threadPool = (ScheduledThreadPoolExecutor) scheduledExecutorService;// 设置false的情况下,会使得调用shutdown()后,未执行的延迟任务不再执行(默认是true,继续执行延迟的任务)
//		threadPool.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
*/System.out.println("Main:开始执行主程序" + new Date());for (int i = 0; i < 5; i++) {Task task = new Task("Task " + i);// 执行task任务,延迟i+1秒执行。scheduledExecutorService.schedule(task, i + 1, TimeUnit.SECONDS);}// shutdown 之后,是否继续执行延迟任务受属性executeExistingDelayedTasksAfterShutdown影响scheduledExecutorService.shutdown();try {// 等待所有任务执行完毕,最长等待2天时间。scheduledExecutorService.awaitTermination(2, TimeUnit.DAYS);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Main:结束执行" + new Date());}}

任务

package xyz.jangle.thread.test.n4_6.schedule;import java.util.Date;
import java.util.concurrent.Callable;/*** 	普通的任务* @author jangle* @email jangle@jangle.xyz* @time 2020年8月21日 下午7:02:27* */
public class Task implements Callable<String> {private final String name;public Task(String name) {super();this.name = name;}@Overridepublic String call() throws Exception {System.out.println(this.name + "开始执行任务" + new Date());return "hello,schedule task";} 
}

上面的代码可以优化下:

package xyz.jangle.thread.test.n4_6.schedule;import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;/*** 示例展示如何使用ScheduledExecutorService安排延时执行的任务。* 五个任务分别延迟1至5秒后执行,主程序等待所有任务完成。* * 注意:若希望在特定时间点执行任务,需计算当前时间至该时间点的时间差。* * @author Jangle* @since 2020-08-21*/
public class ScheduledTaskDemo {public static void main(String[] args) {// 创建单线程的ScheduledExecutorServiceScheduledExecutorService executor = Executors.newScheduledThreadPool(1);System.out.println("主程序开始执行:" + LocalDateTime.now());// 提交五个延迟任务for (int delay = 1; delay <= 5; delay++) {int finalDelay = delay; // 使用final或effectively final变量在lambda表达式中executor.schedule(() -> {System.out.println("执行任务:Task " + finalDelay + " at " + LocalDateTime.now());}, finalDelay, TimeUnit.SECONDS);}// 关闭调度器,不再接受新任务,已提交的任务将继续执行executor.shutdown();try {// 等待所有任务完成,最多等待2天if (!executor.awaitTermination(2, TimeUnit.DAYS)) {System.err.println("等待超时,部分任务未完成。");}} catch (InterruptedException e) {executor.shutdownNow(); // 中断等待,尝试取消正在执行的任务Thread.currentThread().interrupt(); // 保持中断状态System.err.println("主线程被中断。");} finally {System.out.println("主程序执行结束:" + LocalDateTime.now());}}
}

优化点包括:
使用LocalDateTime.now()替代new Date()以获得更易读的日期时间表示。
将类名M改为更具描述性的ScheduledTaskDemo。
注释内容更新,提供更清晰的说明。
在循环内部使用final或实际上的final变量finalDelay,以符合lambda表达式的语法要求。
在awaitTermination后增加处理逻辑,以应对等待超时或线程中断的情况,提高了代码的健壮性。
移除了注释掉的代码片段,保持代码清爽。若需要自定义ScheduledThreadPoolExecutor的行为,应显式创建并配置,而不是向下转型。

如何获取各个定时任务的返回结果呢

其实这个问题我在别的博文中已经详细介绍过了,为了让大家将之前所学的知识串起来,附上链接可以跳转
【CompletableFuture】批量异步任务处理
解决方案一:使用Future来收集结果集
如果您需要获取并打印每个定时任务的返回结果,您需要将Runnable改为Callable,以便任务能返回结果,并使用Future来接收这些结果。下面是修改后的代码示例:

package xyz.jangle.thread.test.n4_6.schedule;import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;public class ScheduledTaskDemoWithResults {public static void main(String[] args) {// 创建单线程的ScheduledExecutorServiceScheduledExecutorService executor = Executors.newScheduledThreadPool(1);System.out.println("主程序开始执行:" + LocalDateTime.now());List<Future<String>> futures = new ArrayList<>();// 提交五个延迟任务,这些任务现在返回一个字符串结果for (int delay = 1; delay <= 5; delay++) {int finalDelay = delay; // 使用final或effectively final变量在lambda表达式中Future<String> future = executor.schedule(() -> {System.out.println("执行任务:Task " + finalDelay + " at " + LocalDateTime.now());return "Result from Task " + finalDelay;}, finalDelay, TimeUnit.SECONDS);futures.add(future); // 收集Future对象}// 关闭调度器,不再接受新任务,已提交的任务将继续执行executor.shutdown();try {// 等待所有任务完成,并收集结果for (Future<String> future : futures) {// get()方法会阻塞直到结果可用,这里可以使用get(long timeout, TimeUnit unit)来设定超时String result = future.get();System.out.println("任务结果:" + result);}} catch (InterruptedException | ExecutionException e) {e.printStackTrace();executor.shutdownNow(); // 中断等待,尝试取消正在执行的任务Thread.currentThread().interrupt(); // 保持中断状态System.err.println("主线程被中断或任务执行时出现异常。");} finally {System.out.println("主程序执行结束:" + LocalDateTime.now());}}
}

解决方案二:使用CompletableFuture.allOf()来收集结果集
使用 java.util.concurrent.CompletableFuture 和 CompletableFuture.allOf() 方法是一种更现代且灵活的方式来管理异步任务,尤其是当你需要等待一组任务全部完成后再继续执行后续操作时。下面是如何在你的场景中使用 CompletableFuture 和 allOf() 的示例:

package xyz.jangle.thread.test.n4_6.schedule;import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;public class ScheduledTaskDemoWithAllOf {public static void main(String[] args) throws InterruptedException, ExecutionException {// 创建单线程的ScheduledExecutorServiceExecutorService executor = Executors.newSingleThreadScheduledExecutor();System.out.println("主程序开始执行:" + LocalDateTime.now());List<CompletableFuture<String>> futures = new ArrayList<>();// 提交五个延迟任务,使用CompletableFuture来代表异步操作for (int delay = 1; delay <= 5; delay++) {int finalDelay = delay;CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {System.out.println("执行任务:Task " + finalDelay + " at " + LocalDateTime.now());return "Result from Task " + finalDelay;},executor // 使用自定义的Executor).orTimeout(2, TimeUnit.SECONDS); // 可选:为每个任务设置超时futures.add(future);}// 创建一个 CompletableFuture,它将在所有输入的CompletableFuture完成后完成CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));// 当所有任务都完成时,打印所有结果allFutures.thenAccept(v -> {for (CompletableFuture<String> future : futures) {try {String result = future.get(); // 这里会立即返回,因为我们已经知道所有任务都完成了System.out.println("任务结果:" + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}System.out.println("所有任务已完成:" + LocalDateTime.now());});// 关闭ExecutorServiceexecutor.shutdown();executor.awaitTermination(2, TimeUnit.DAYS); // 等待所有任务完成System.out.println("主程序执行结束:" + LocalDateTime.now());}
}

执行结果

Main:开始执行主程序Fri Aug 21 19:19:40 CST 2020
Task 0开始执行任务Fri Aug 21 19:19:41 CST 2020
Task 1开始执行任务Fri Aug 21 19:19:42 CST 2020
Task 2开始执行任务Fri Aug 21 19:19:43 CST 2020
Task 3开始执行任务Fri Aug 21 19:19:44 CST 2020
Task 4开始执行任务Fri Aug 21 19:19:45 CST 2020
Main:结束执行Fri Aug 21 19:19:45 CST 2020

总结

本篇博文的主题中心还是和大家介绍下定时任务如何使用且大家常见的用法之间的联系,各个spring底层封存的工具类有什么联系包括使用陷阱,其实解决的场景只有一个就是为了延迟处理任务,除了使用定时任务线程池之外,还有其他更好的方案【如何优雅的实现延迟消息多次提醒】方案集合

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/840555.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git时光机、Git标签、Git分支、GitHub协作

Git时光机&#xff08;切换版本&#xff09; 1.查看提交历史 HEAD指针指向这次分支的最后一次提交 版本信息一行显示【git log --prettyoneline】 2.引用日志【git reflog】 &#xff08;只在自己的工作区中存在&#xff09; 非常重要&#xff1a;当HEAD指针进行切换之后&…

重学java 43.多线程 多等待多唤醒案例

Fear never builds the future,but hope does. —— 24.5.25 多等待多唤醒问题 在多条线程同时消费同时等待时&#xff0c;会出现问题 BaoZiPu package S77ThreadMoreWait;/*count和flag可以定义成包装类&#xff0c;但要记得给count和flag手动赋值不然对于本案例来说&#xff…

python低阶基础100题(上册)

** python低阶基础100题&#xff08;上册&#xff09; ** 1. 请打印出字符串 Hello World print("Hello World")2. 请打印出字符串 爸爸妈妈&#xff0c;你们辛苦啦 print("爸爸妈妈&#xff0c;你们辛苦啦")3. 请打印出字符串 人生苦短&#xff0c;我…

SVN创建分支,分支合并,切换分支。通俗易懂

1、首先在svnbucket.com远程仓库上创建项目&#xff0c;这里我创建了个测试demo&#xff1a; 2、先把svn仓库的项目检出到自己的文件夹&#xff0c;我这里是demo001文件夹&#xff0c;此时并没有创建truck, branches, tags这三个目录&#xff1a; 3、 在demo001文件夹里新建tru…

vue实战 ---- 社交媒体---黑马头条项目

vue基础 1.介绍 为什么会有Vuex ? ​ Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 vuex是采用集中式管理组件依赖的共享数据的一个工具&#xff0c;可以解…

spring boot打的包直接运行

Spring Boot 提供了一个插件 spring-boot-maven-plugin 把程序打包成一个可执行的jar包&#xff0c;直接执行java -jar xxx.jar即可以启动程序 1、引用 spring-boot-maven-plugin插件 <build><plugins><plugin><groupId>org.springframework.boot<…

Hive安装教程

前置条件:hadoop&mysql docker容器安装mysql-CSDN博客 以下的/opt/bigdata目录根据自己实际情况更改 1.上传hive包并解压 tar -zxvf apache-hive-3.1.3-bin.tar.gz -C /opt/bigdata/ 2.修改路径 mv /opt/bigdata/apache-hive-3.1.3-bin/ hive cd /opt/bigdata/hive/…

全网最全网络基础思维导图合集(38张)

计算机网络基础知识点多且杂&#xff0c;想要系统地学习&#xff0c;思维导图肯定是必不可少的。 今天整理了38张思维导图&#xff0c;帮助你轻松理清思路&#xff0c;快速掌握关键内容。建议你收藏起来慢慢看&#xff0c;在看过之后最好能重新动手画一画&#xff0c;让计算机…

【数据结构】线性表习题 |顺序表 |链表 |栈和队列

&#x1f4d6;专栏文章&#xff1a;数据结构学习笔记 &#x1faaa;作者主页&#xff1a;格乐斯 前言 线性表习题 |顺序表 |链表 |栈和队列 顺序表和链表 1、 选B 1002(5-1)108* 第i个元素地址X&#xff0c;元素长度Len&#xff0c;第j个元素地址Y 公式&#xff1a;YXL…

Windows 7 SP1 安装VMtools -- 安装失败的解决方法

VMware安装Win7 SP1可以参考这篇文章&#xff1a;https://blog.csdn.net/2301_77225571/article/details/139121179?spm1001.2014.3001.5501 1.下载补丁 https://www.catalog.update.microsoft.com/search.aspx?qkb4474419 2.本机远控Win7 【Win】【R】&#xff0c;输入cmd…

第二十届文博会沙井艺立方分会场启幕!大咖齐打卡!

2024年5月24日-27日&#xff0c;第二十届中国&#xff08;深圳&#xff09;国际文化产业博览交易会沙井艺立方分会场活动将在艺立方非遗&#xff08;文旅&#xff09;产业园盛大举办。 本届文博会艺立方分会场活动办展特色鲜明&#xff0c;亮彩纷呈&#xff0c;将以“种下梧桐树…

aws eks理解和使用podidentity为pod授权

参考链接 https://www.amazonaws.cn/new/2024/amazon-eks-introduces-eks-pod-identity/https://aws.amazon.com/cn/blogs/aws/amazon-eks-pod-identity-simplifies-iam-permissions-for-applications-on-amazon-eks-clusters/ 先决条件 集群版本需要符合要求&#xff0c;如果…

【揭秘!在线ChatGPT神器,体验入口在此!】

&#x1f680;【揭秘&#xff01;在线ChatGPT神器&#xff0c;体验入口在此&#xff01;】&#x1f680; 前言 嘿&#xff0c;大家好&#xff01;今天我要和大家分享一些关于如何使用免费的ChatGPT的技巧。ChatGPT是一项令人兴奋的人工智能技术&#xff0c;它可以成为我们的好…

aws eks集成wasm运行时并启动pod

参考资料 WebAssembly 在云原生中的实践指南&#xff0c;https://cloud.tencent.com/developer/article/2324065 作为一种通用字节码技术&#xff0c;wasm的初衷是在浏览器中的程序实现原生应用性能。高级语言将wasm作为目标语言进行编译并运行在wasm解释器中。和nodejs类似的…

文件自动同步备份-FreeFileSync工具解决硬盘损坏、误操作覆盖导致数据丢失

文件自动同步备份-FreeFileSync工具解决硬盘损坏、误操作覆盖导致数据丢失 文章目录 文件自动同步备份-FreeFileSync工具解决硬盘损坏、误操作覆盖导致数据丢失前言一、FreeFileSync二、使用方法1.用外部存储卡或盘作为异地备份目标盘2.设置同步策略3.设置为windows的自动计划 …

将电脑D盘部分空间划分给C盘的方法

本文介绍在Windows电脑中&#xff0c;将D盘的部分空间分给C盘的方法。 最近&#xff0c;发现电脑中C盘的空间剩余不多了&#xff1b;而D盘由于当初分盘时划分的空间过多&#xff0c;导致其剩余空间很大且大概率以后都不会用上D盘中这些多余的空间了。因此&#xff0c;希望将D盘…

ubuntu20.04 安装系统后-开机黑屏-nvidia显卡驱动没问题_thinkpad-intel-13700H

文章目录 硬件现象原因&解决 硬件 thinkpad p1 gen6笔记本&#xff0c; intel 13代cpu 13700H,nvidia rtx 2000 Ada laptop gpu 13700H应该是有集显的&#xff0c;但可能没装集显驱动or由于Bios设置的缘故&#xff0c;我的win任务管理器只能看到一个gpu(gpu0)&#xff1…

学会这些大模型术语,你可以硬控朋友7.13分钟

你对于大型语言模型&#xff08;LLMs&#xff09;的复杂世界以及围绕它们的技术术语感到好奇吗&#xff1f; 理解从训练和微调的基础方面到转换器和强化学习的尖端概念&#xff0c;是揭开驱动现代人工智能大语言模型神秘面纱的第一步。 在本文中&#xff0c;我们将深入探讨 25 …

【软件设计师】大题

一、数据流图 基础知识 数据流图&#xff08;Data Flow Diagram,DFD&#xff09;基本图形元素&#xff1a; 外部实体&#xff08;External Agent&#xff09; 表示存在于系统之外的对象&#xff0c;用来帮助用户理解系统数据的来源和去向加工&#xff08;Process&#xff09;数…

网络安全基础技术扫盲篇 — 名词解释

网络模块基础&#xff08;网络拓扑图、网络设备、安全设备&#xff09; 用通俗易懂的话说&#xff1a; 网络拓扑图&#xff1a;它就像一张网络世界的地图&#xff0c;它展现了我们数不清的网站、服务器和设备是如何相互连接的。用简单的话说&#xff0c;它就是给我们指路、告…