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

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

 

写在最后

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

 相关推荐:

posted @ 2023-09-08 16:08  Marydon  阅读(578)  评论(0编辑  收藏  举报