纯java 实现定时任务的两种方式 2025753编辑
Heaven helps those who help themselves
资深码农+深耕理财=财富自由
欢迎关注
资深码农+深耕理财=财富自由
欢迎关注

纯java 实现定时任务的两种方式
Created by Marydon on 2023-09-08 16:08
1.情景展示
在实际项目开发过程中,往往会存在这样的需求:
定时执行某个任务,如何实现?
2.具体分析
定时任务,其实就是定时调用。
在代码中,我们可以通过定时运行某个类的某个方法来实现。
具体实现方式,有两种:
一种是通过java实现。
另一种是借助spring来实现。
本文只说java实现方式。
3.解决方案
通过java原生代码,有两种实现方式。
方式一:使用java.util.Timer类
Timer类用于在指定时间间隔后执行任务一次或重复执行。它不能保证任务在准确的时间执行,但能保证任务至少不会比指定的时间间隔提前执行。
这是百度AI给出的示例。
此定时类无法在指定时间运行的话,就显得十分鸡肋了。
故而,不推荐大家使用这种方法。
方式二:使用java.util.concurrent.ScheduledExecutorService
ScheduledExecutorService接口用于在给定的延迟后运行命令,或者定期执行命令。它比Timer类更强大,提供了更多的调度选项。
每天早上8点,执行任务(定时上传)。
UploadEveryDay.java
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import base.web.tools.DateUtils;
public class UploadEveryday { private Logger log = Logger.getLogger(this.getClass()); /** * 为Runnable填加一个方便其它类调用的壳子(方法) */ public void upload() { Runnable task = new Runnable() { /** * 任务内容 * @explain 每天都会上传24小时以内的数据 * @description 将具体要运行的任务内容写在本方法里(run()) */ public void run() { // task to run goes here log.info("任务执行时间为:" + DateUtils.getSysdateStr("yyyy-MM-dd HH:mm:ss")); try { UploadTask ut = new UploadTask(); // 上传24小时数据 ut.searchParamsCode("2"); } catch (Exception e) { log.error("数据上传失败!"); log.error(e.getMessage()); e.printStackTrace(); } } }; /** * 计算距离当前时间,还有多长时间执行任务! 如果不需要可以删除 */ Calendar calendar = Calendar.getInstance(); // 获取当前日期 String curDateStr = DateUtils.getSysdateStr("yyyy-MM-dd"); // 系统当前时间所对应的毫秒数 long curMillis = calendar.getTimeInMillis(); // 获取系统当前是几点 int curHour = calendar.get(calendar.HOUR_OF_DAY);// 24小时制 // 指定要执行的时间(早上8点) int specHour = 8; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH"); // 任务执行的年月日+小时 Date setDate = null; // 任务执行时所对应的毫秒数 long setMillis = 0; // 执行时间 long exeTime = 0; try { if (curHour <= specHour) {// 当前时间在specHour:00之前 // 将指定字符串转换成日期 setDate = sdf.parse(curDateStr + " " + specHour); setMillis = setDate.getTime(); // 还差多长时间 exeTime = setMillis - curMillis; } else {// 当前时间在specHour:00之后 // 后一天:当前日期+1 calendar.add(Calendar.DAY_OF_MONTH, 1); SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd"); String setDateStr = sdf2.format(calendar.getTime()); // 将指定字符串转换成日期 setDate = sdf.parse(setDateStr + " " + specHour); setMillis = setDate.getTime(); // 还差多长时间 exeTime = setMillis - curMillis; } } catch (ParseException e) { log.error("数据上传失败!"); log.error(e.getMessage()); e.printStackTrace(); return;// 结束方法运行 } // ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); // 获取任务执行器 ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); // 延迟5秒后执行一次任务 // executorService.schedule(task, 5, TimeUnit.SECONDS); // 线程/延迟执行时间/多长时间执行一次/时间单位(第二个参数如果为负数,会立即执行) executorService.scheduleAtFixedRate(task, exeTime, 24 * 60 * 60 * 1000, TimeUnit.MILLISECONDS); // 时间单位 String timeUnit = ""; // 首次执行剩余时间 int remaTime = 0; // Java整数间的除法运算,默认只保留整数位 double hours = exeTime / 1000 / 3600; double minutes = exeTime / 1000 / 60; double seconds = exeTime / 1000; // hours,minutes,seconds的结果只可能为0.0/1.0/2.0等(即小数位永远为0),不可能为:0.1/1.1/2.1 if (hours > 0) { remaTime = (int) hours; timeUnit = "小时"; } else if (minutes > 0) { remaTime = (int) minutes; timeUnit = "分钟"; } else { remaTime = (int) seconds; timeUnit = "秒"; } log.info("启动定时器...UploadEveryday...距离任务执行还有" + remaTime + timeUnit + "!"); } }
运行的任务内容详情:(具体执行任务的类)
UploadTask.java
查看代码
import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import com.sinosoft.ie.usecard.client.UploadClient; import com.sinosoft.ie.usecard.client.UsingCardMessage; import base.service.bo.dataUpload.IBoVIRTUAL_CARD; import base.service.domain.exception.NoSuchBeanException; import base.service.domain.tools.BeansHelp; import base.web.actions.BaseAction; public class UploadTask extends BaseAction { private Logger logger = Logger.getLogger(this.getClass()); private IBoVIRTUAL_CARD boVIRTUAL_CARD; public UploadTask() { try { // 虚拟卡系统 boVIRTUAL_CARD = (IBoVIRTUAL_CARD) BeansHelp.getBeanInstance("boVIRTUAL_CARD"); } catch (NoSuchBeanException e) { logger.error(e.getMessage()); } } /** * 数据上传 * @explain */ public void dataUpload() { UploadAllData uad = new UploadAllData(); // 1.项目启动后,上传所有未上传的数据 boolean isOver = uad.upload(); // 2.上传结束,执行定时上传任务 if (isOver) { UploadEveryday ue = new UploadEveryday(); ue.upload(); } } /** * 查询条件配置 * @explain * @param model 1-上传所有;2-上传24小时 * @throws Exception */ public void searchParamsCode(String model) throws Exception { Map<String, String> paramsMap = new HashMap<>(5); // 上传所有 paramsMap.put("model", model); // 统计所有未上传的数据(计数) Map<String, BigDecimal> resultMap = boVIRTUAL_CARD.getUploadVIRTUAL_CARDDataCount(paramsMap); // 需要上传总数 long total = resultMap.get("TOTAL").longValue(); // 需要上传数据的id-最小 long minValue = resultMap.get("MINVALUE").longValue(); // 需要上传数据的id-最大 long maxValue = resultMap.get("MAXVALUE").longValue(); int targetSize = 100; // 上升为double类型,供运算使用 double targetSize2 = targetSize; // 需要从数据库读取数据的次数(非整数+1)每次从数据库查询100条数据 int databaseTimes = (int) Math.ceil(total / targetSize2); // 查询分页数据 int start = 0; int end = 0; // orcle会自动将int-->String的转换 String s_start = ""; String s_end = ""; // 上传24小时数据(已经添加到了sql中,所以这里不用再配置时间条件) log.info("总共需要上传:" + databaseTimes + "次!"); // 1.100条一读取,然后上传 for (int i = 1; i <= databaseTimes; i++) { start = (i - 1) * targetSize + 1; end = i * targetSize; s_start = String.valueOf(start); s_end = String.valueOf(end); paramsMap.put("START", s_start); paramsMap.put("END", s_end); // 循环上传 uploadCommonCoreCode(paramsMap); log.info("累计上传成功:" + end + "条!待上传数据剩余:" + (total - end) + "条!"); } log.info("数据已上传完毕,共计:" + total + "条!"); // 2.数据上传完毕,批量更新 paramsMap.clear(); paramsMap.put("minValue", String.valueOf(minValue)); paramsMap.put("maxValue", String.valueOf(maxValue)); paramsMap.put("isUpload", "1"); // 批量更新:已上传标识 boVIRTUAL_CARD.updateVIRTUAL_CARDLOG(paramsMap); log.info("数据已批量更新完毕,共计:" + total + "条!"); } /** * 数据上传核心代码 * @explain * @param paramsMap * @throws Exception */ public void uploadCommonCoreCode(Map<String, String> paramsMap) throws Exception { log.info("开始执行上传...................."); // 最多有100条数据 List<Map<String, String>> dataList = boVIRTUAL_CARD.getUploadVIRTUAL_CARDData(paramsMap); int num = 0; List<UsingCardMessage> usingCardMessageList = new ArrayList<>(); UsingCardMessage msg = null; // 循环插入 for (int i = 0; i < dataList.size(); i++) { num++; Map<String, String> cardMap = dataList.get(i); msg = new UsingCardMessage(); // 用卡时间 msg.setTime(cardMap.get("TIME")); // 电子健康卡 msg.setCardType("0"); // 身份证号 msg.setAtr(cardMap.get("ATR")); // 发卡机构代码 msg.setIssueOrgCode(cardMap.get("ISSUEORGCODE")); // 发卡机构名称 msg.setIssueOrgName(cardMap.get("ISSUEORGNAME")); // 健康卡卡号 msg.setHcNumber(cardMap.get("HCNUMBER")); // 扫码枪终端标识号 msg.setSam(cardMap.get("SAM")); // 用卡城市代码 msg.setUseCityCode(cardMap.get("USECITYCODE")); // 用卡城市名称 msg.setUseCityName(cardMap.get("USECITYNAME")); // 民族代码 msg.setNation(cardMap.get("NATION")); // 身份证号 msg.setIDCard(cardMap.get("IDCARD")); // 医疗机构代码 msg.setHospitalCode(cardMap.get("HOSPITALCODE")); // 医疗机构名称 msg.setHospitalName(cardMap.get("HOSPITALNAME")); // 刷卡终端类型编号 msg.setChannelCode(cardMap.get("CHANNELCODE")); // 刷卡终端类型 msg.setChanelName(cardMap.get("CHANELNAME")); // 诊疗环节代码 msg.setMedStepCode(cardMap.get("MEDSTEPCODE")); // 诊疗环节名称 msg.setMedStepName(cardMap.get("MEDSTEPNAME")); usingCardMessageList.add(msg); // 100条上传1次:不足100条,将不走该条件 if (num % 100 == 0) { log.info("上传中......1"); // 批量执行预定义SQL UploadClient.upload(usingCardMessageList); usingCardMessageList.clear(); } } // 不足100条数据将走该条件 if (!usingCardMessageList.isEmpty()) { log.info("上传中......2"); UploadClient.upload(usingCardMessageList); } } }
UploadAllData.java
package base.web.actions.upload; import org.apache.log4j.Logger; /** * 上传所有数据 * @explain 所有没有上传的数据 * @author Marydon * @creationTime 2019年6月13日下午4:29:19 * @version 1.0 * @since * @email marydon20170307@163.com */ public class UploadAllData { private Logger log = Logger.getLogger(this.getClass()); /** * 上传所有数据 * @explain 上传所有未上传的数据 * @return 是否上传结束 */ public boolean upload() { // 是否结束 boolean isOver = false; try { UploadTask ut = new UploadTask(); // 上传所有数据 ut.searchParamsCode("1"); isOver = true; } catch (Exception e) { log.error("数据上传失败!"); log.error(e.getMessage()); e.printStackTrace(); } return isOver; } }
4.定时任务启动方式
让定时任务生效的时机,也就是什么时候调此定时任务呢?
一般情况下,我们会在项目启动的时候就会让定时任务生效。
我们可以通过Servlet启动。
第一步:创建Servlet
import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; /** * web项目启动后,调用该类 * @explain * @author Marydon * @creationTime 2019年4月3日下午2:21:51 * @version 1.0 * @since * @email marydon20170307@163.com */ public class TaskExecutor extends HttpServlet { private static final long serialVersionUID = 1L; /** * tomcat容器启动后,将会调用该方法 */ public void init() throws ServletException { super.init(); // 项目启动后,立即执行上传任务 UploadTask sb = new UploadTask(); // 数据上传 sb.dataUpload(); } }
第二步:将其配置到web.xml当中
<!-- 服务器一启动,就执行Java类 --> <!-- 将数据上传到国家数据中心 --> <servlet> <servlet-name>uploadServlet</servlet-name> <servlet-class>base.web.actions.upload.TaskExecutor</servlet-class> <!-- 被加载到servlet容器的优先级 --> <load-on-startup>1</load-on-startup> </servlet>
5.拓展
另外两种定时任务实现方式
方式三:使用Spring的@Scheduled注解
更简单的定时任务实现方式和调用方式,自然是:
利用spring组件来实现,这也是我们目前使用的最佳方式。
使用注解@Scheduled来创建定时任务。
这种方式需要在Spring配置类中启用定时任务。
方式四:使用Quartz库
Quartz是一个开源的全功能的在Java环境中使用的作业调度服务。它允许你创建复杂的调度规则,如每周、每天、每小时或每分钟执行一次任务。Quartz使用起来比较复杂,但提供了丰富的调度功能。
与君共勉:最实用的自律是攒钱,最养眼的自律是健身,最健康的自律是早睡,最改变气质的自律是看书,最好的自律是经济独立 。
您的一个点赞,一句留言,一次打赏,就是博主创作的动力源泉!
↓↓↓↓↓↓写的不错,对你有帮助?赏博主一口饭吧↓↓↓↓↓↓
本文来自博客园,作者:Marydon,转载请注明原文链接:https://www.cnblogs.com/Marydon20170307/p/17687875.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
2019-09-08 java 压缩图片(只缩小体积,不更改图片尺寸)
2019-09-08 js 压缩图片(只缩小体积,不更改图片尺寸)