首页 > 编程语言 >纯java 实现定时任务的两种方式

纯java 实现定时任务的两种方式

时间:2023-09-08 16:24:16浏览次数:42  
标签:两种 java get cardMap msg import 定时 上传

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 [email protected]
 */
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 [email protected]
 */
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使用起来比较复杂,但提供了丰富的调度功能。

 

写在最后

  哪位大佬如若发现文章存在纰漏之处或需要补充更多内容,欢迎留言!!!

 相关推荐:

标签:两种,java,get,cardMap,msg,import,定时,上传
From: https://www.cnblogs.com/Marydon20170307/p/17687875.html

相关文章

  • javascript | 变量、函数、属性的命名规则
    javascript标识符的命名规则变量、函数、属性的名字、或者函数的参数,都可称为标识符。标识符可以是按照下列格式规则组合起来的一个或者多个字符。第一个字符必须是一个字母、下划线_、或美元符号$。数字不可以作为标识符的首字符。其他字符可以是数字、字母、下划线_、或美......
  • java笔试题,寻找多出来的元素
    题目:有两个数组a和b,其中b有一个元素是a没有的,其他元素都相同,请找出b中这个多余的元素。1publicclassTest02{2 3publicstaticvoidmain(String[]args){4int[]a={11,34,9,-4,100,98};5int[]b={34,55,11,9,100,-4,98};67intc=0;8for(inti=0......
  • java设计模式,简单工厂和抽象工厂有什么区别?
    java设计模式,简单工厂和抽象工厂有什么区别?简单工厂模式:这个模式本身很简单而且使用在业务较简单的情况下。一般用于小项目或者具体产品很少扩展的情况(这样工厂类才不用经常更改)。它由三种角色组成:工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工......
  • java开发之个微机器人的二次开发
    简要描述:修改我在某群的昵称请求URL:http://域名/updateIInChatRoomNickName请求方式:POST请求头Headers:Content-Type:application/jsonAuthorization:login接口返回参数:参数名必选类型说明wId是String登录实例标识请求参数示例{"wId":"4941c159-48dc-4271-b0d0-f94adea39127",......
  • java 支持 超大上G,多附件上传实例
    ​ 这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数下面直接贴代码吧,一些难懂的我大部分都加上注释了:上传文件实体类:看得出来,实体类中已经有很多我们需要的功能了,还有实用的属性。如MD5秒传的信息。pub......
  • java正则表达式(一)
    转: https://www.cnblogs.com/-w-k-/p/16040896.html一、校验数字的表达式1数字:^[0-9]*$2n位的数字:^\d{n}$3至少n位的数字:^\d{n,}$4m-n位的数字:^\d{m,n}$5零和非零开头的数字:^(0|[1-9][0-9]*)$6非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$7带......
  • Python 框架(Flask,tornado,fastAPI)Go 的gin框架 Java spring 框架中的性能对比
    使用jmeter进行压测:配置如下: Flask框架:Python代码:fromflaskimportFlaskapp=Flask(__name__)@app.route('/')defhello_world():return'Hello,World!'if__name__=='__main__':app.run(port=8080)测试结果: Tornado......
  • 无涯教程-JavaScript - IMCOSH函数
    描述IMCOSH函数以x+yi或x+yj文本格式返回复数的双曲余弦值。语法IMCOSH(inumber)争论Argument描述Required/OptionalInumberAcomplexnumberforwhichyouwantthehyperboliccosine.RequiredNotesExcel中的复数只是简单地以文本形式存储在Excel中。......
  • Sermant类隔离架构:解决JavaAgent场景类冲突的实践
    本文分享自华为云社区《Sermant类隔离架构解析——解决JavaAgent场景类冲突的实践》,作者:华为云开源。 一、JavaAgent场景为什么要注意类冲突问题?类冲突问题并非仅存在于JavaAgent场景中,在Java场景中一直都存在,该问题通常会导致运行时触发NoClassDefFoundError、ClassNotFoundExcep......
  • Java并发篇:6个必备的Java并发面试种子题目
    线程创建和生命周期线程的创建和生命周期涉及到线程的产生、执行和结束过程。让我们继续深入探索这个主题:线程的创建方式有多种,你可以选择适合你场景的方式:继承Thread类:创建一个类,继承自Thread类,并重写run()方法。通过实例化这个类的对象,并调用start()方法,系统会自动调用run()方法......