JAVA版本:jdk1.8,代码中有使用Lambda语法糖。

数据库:MySQL

框架:Spring Data

开发工具:IDEA 2017.3.2

Lombok

PS:

1. 主要是结合Spring Boot一起使用,并在Spring Boot启动的时候一起启动运行。

2. 和数据库结合使用的主要目的是在程序运行的时候,可以通过操作数据库对定时任务的控制,例如关闭和启动任务,添加任务,修改定时时间等等。

3. 查看所有任务,错误日志和运行日志等等。

4. 该定时器最初被设计的目的是用于每天晚上0点定时爬取某音乐平台的热门音乐绑定,新音乐榜单等信息。

使用方法:

1. 创建相应的数据库表(后续会自动创建)
2. 创建一个Runnable继承MyRunnable抽象类
3. 将该类的全名(包括包名)存储到表中,并插入相应的任务名称,开始时间和时间表达式,即可。



1. 总体思路

1.1 使用一个线程查询数据库,将还未完成的任务查找出来(每5s查一次)
1.2 将结果存储到阻塞队列中(通过反射获取runnable)
1.3 另外的线程循环获取阻塞队列中的runnable,
1.4 在运行runnable之前,更新数据库
1.5 运行runnable
1.6 写入日志
1.7 如果出错,写入错误日志
复制代码

2. 数据库结构

任务列表库

  DROP TABLE IF EXISTS `mytask`;CREATE TABLE `mytask` (`id` int(11) NOT NULL AUTO_INCREMENT,`taskname` varchar(255) NOT NULL COMMENT '任务名称',`status` int(1) DEFAULT NULL COMMENT '状态',`starttime` datetime DEFAULT NULL COMMENT '开始时间',`nexttime` datetime DEFAULT NULL COMMENT '下一次运行时间',`begintime` datetime DEFAULT NULL COMMENT '起始时间',`classname` varchar(255) DEFAULT NULL COMMENT '类名,必须是全名,包含包名',`expression` varchar(20) DEFAULT NULL COMMENT '时间表达式',PRIMARY KEY (`id`),UNIQUE KEY `index_id` (`id`) USING BTREE,UNIQUE KEY `index_taskname` (`taskname`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;# 插入数据INSERT INTO `mytask` VALUES ('1', 'hotting_music', '1', '2018-01-28 13:26:51', '2018-01-29 13:26:51', '2018-01-27 22:49:35', 'com.wsk.movie.task.runnable.MusicHottingTaskRunnable', '00 00 00 1');INSERT INTO `mytask` VALUES ('2', 'hot_music', '1', '2018-01-28 00:52:42', '2018-01-28 00:52:54', '2018-01-28 13:53:03', 'com.wsk.movie.task.runnable.MusicHotTaskRunnable', '00 00 00 1');INSERT INTO `mytask` VALUES ('3', 'new_music', '1', '2018-01-28 00:53:53', '2018-01-28 00:54:02', '2018-01-28 13:54:00', 'MusicNewTaskRunnable', '00 00 00 1');复制代码

错误信息库

    DROP TABLE IF EXISTS `mytaskerror`;CREATE TABLE `mytaskerror` (`id` int(11) NOT NULL AUTO_INCREMENT,`taskname` varchar(255) DEFAULT NULL COMMENT '定时任务名',`rtime` datetime DEFAULT NULL COMMENT '发生时间',`msg` varchar(2000) DEFAULT NULL COMMENT '错误信息',`classname` varchar(255) DEFAULT NULL COMMENT '类名',PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码

日志列表库

 DROP TABLE IF EXISTS `mytasklog`;CREATE TABLE `mytasklog` (`id` int(11) NOT NULL AUTO_INCREMENT,`taskname` varchar(255) DEFAULT NULL,`rtime` datetime DEFAULT NULL,`classname` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;复制代码

3. 项目结构

taskentity:数据库实体类queue:存储runnable的队列runnable:运行的类service:操作数据库tool:工具类-主要是日期表达式的转化
复制代码

4. 实操

1. 执行sql语句,创建task相关的表

2. 使用idea连接数据库,并根据数据库task相关的表生成对应的实体,存放于entity包中

3. 创建service包和相应的接口

3.1 错误日志记录接口
package com.wsk.movie.task.service;import com.wsk.movie.task.entity.MytaskerrorEntity;
import org.springframework.data.jpa.repository.JpaRepository;/*** @DESCRIPTION :错误任务记录* @AUTHOR : WSK1103* @TIME : 2018/1/24  22:43*/
public interface MyErrorTaskRepository extends JpaRepository<MytaskerrorEntity, Integer> {
}复制代码
3.2 平时日志记录接口
package com.wsk.movie.task.service;import com.wsk.movie.task.entity.MytasklogEntity;
import org.springframework.data.jpa.repository.JpaRepository;/*** @DESCRIPTION :平时日志记录* @AUTHOR : WuShukai1103* @TIME : 2018/1/27  22:39*/
public interface MyTaskLogRepository extends JpaRepository<MytasklogEntity, Integer> {
}复制代码
3.3 任务表接口
package com.wsk.movie.task.service;import com.wsk.movie.task.entity.MytaskEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;import javax.transaction.Transactional;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;/*** @DESCRIPTION :任务表接口* @AUTHOR : WuShukai1103* @TIME : 2018/1/23  23:02*/
public interface MyTaskRepository extends JpaRepository<MytaskEntity, Integer> {MytaskEntity findByTaskname(String taskname);//更新@Transactional@Query(value = "update MytaskEntity m set m.starttime = :start,m.nexttime = :next where m.taskname = :name")@Modifyingvoid updateTime(@Param("name") String name, @Param("start") Date start, @Param("next") Date next);//更新@Transactional@Query(value = "update MytaskEntity m set m.starttime = :start,m.nexttime = :next where m.taskname = :name")@Modifyingvoid updateTime(@Param("name") String name, @Param("start") Timestamp start, @Param("next") Timestamp next);//根据任务名关闭@Transactional@Query(value = "update MytaskEntity m set m.status = :status where m.taskname = :name")@Modifyingvoid updateStatus(@Param("name") String name, @Param("status") int status);//根据id关闭@Transactional@Query(value = "update MytaskEntity m set m.status = :status where m.taskname = :id")@Modifyingvoid updateStatus(@Param("id") int id, @Param("status") int status);//待执行的任务@Query(value = "select m from MytaskEntity m where m.status = 1")List<MytaskEntity> starts();}复制代码

4.创建MyRunnable抽象类,该类继承Runnable,自定义的线程都必须继承该类

package com.wsk.movie.task.runnable;import lombok.Data;/*** @DESCRIPTION :* @AUTHOR : WuShukai1103* @TIME : 2018/1/23  23:05*/
@Data
public abstract class MyRunnable implements Runnable {
}复制代码
4.1 创建几个运行测试
package com.wsk.movie.task.runnable;import com.wsk.movie.music.HttpUnits;import java.io.IOException;/*** @DESCRIPTION :音乐定时器,云音乐热歌榜* @AUTHOR : WuShukai1103* @TIME : 2018/1/27  13:48*/
public class MusicHotTaskRunnable extends MyRunnable {@Overridepublic void run() {try {//HttpUnits是自定义的一个读取json的工具类System.out.println(HttpUnits.urlToString("http://localhost:8080/search/music/hot/1").toString());} catch (IOException e) {e.printStackTrace();}}
}复制代码
4.2 创建删除队列中已经运行过的队列key,使数据库查询的相应任务可以添加到队列中
package com.wsk.movie.task.runnable;import com.wsk.movie.task.entity.MytaskEntity;
import com.wsk.movie.task.queue.MyQueue;/*** @DESCRIPTION :* @AUTHOR : WuShukai1103* @TIME : 2018/1/29  21:16*/
public class DelKeyRunnable extends MyRunnable {private MytaskEntity entity;public DelKeyRunnable(MytaskEntity entity) {this.entity = entity;}@Overridepublic void run() {System.out.println("del:" + entity.getTaskname());MyQueue.getInstance().removeKey(entity.getTaskname());}
}复制代码

5. 创建queue包和对应的队列

5.1 创建MyQueue类,该类是用来存储任务的,而且是单例模式
package com.wsk.movie.task.queue;import lombok.EqualsAndHashCode;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;/*** @DESCRIPTION :队列,用来存储任务,单例* ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。* LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。* PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。* DelayQueue:一个使用优先级队列实现的无界阻塞队列。* SynchronousQueue:一个不存储元素的阻塞队列。* LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。* LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。* @AUTHOR : WuShukai1103* @TIME : 2018/1/24  20:53*/
@EqualsAndHashCode
public class MyQueue {//使用无界限阻塞队列private LinkedTransferQueue<MyQueueBean> queue;//存放数据库唯一名称,根据名称判断任务是否重复private static final List<String> LIST = new ArrayList<>();private MyQueue() {queue = new LinkedTransferQueue<>();}private static class NestClass {private static final MyQueue QUEUE = new MyQueue();}public static MyQueue getInstance() {return NestClass.QUEUE;}public void offer(MyQueueBean bean) {//加锁,在多线程的情况下防止多加任务synchronized (LIST) {if (LIST.contains(bean.getEntity().getTaskname())) {System.out.println("重复" + bean.getRunnable().getClass().getName());return;}}LIST.add(bean.getEntity().getTaskname());queue.offer(bean);
//        LIST.forEach(System.out::println);}public MyQueueBean take() throws InterruptedException {//阻塞获取return queue.take();}public void removeKey(MyQueueBean bean){LIST.remove(bean.getEntity().getTaskname());}public void removeKey(MytaskEntity entity) {LIST.remove(entity.getTaskname());}public void removeKey(int id){LIST.remove(id);}public boolean hasNext() {return queue.iterator().hasNext();}public int size(){return queue.size();}}复制代码
5.2 创建MyQueueBean,存储任务和任务的属性,队列存储的是该类,线程运行的是该类中的runnable
package com.wsk.movie.task.queue;import com.wsk.movie.task.entity.MytaskEntity;
import com.wsk.movie.task.runnable.MyRunnable;
import lombok.Data;
import lombok.experimental.Accessors;/*** @DESCRIPTION :存储任务和任务的属性* @AUTHOR : WuShukai1103* @TIME : 2018/1/27  23:57*/
@Accessors(chain = true)
@Data
public class MyQueueBean {private MyRunnable runnable;private MytaskEntity entity;
}复制代码

6. 创建时间表达式解析类

package com.wsk.movie.task.tool;import com.wsk.movie.tool.Tool;import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;/*** @DESCRIPTION :将时间表达式转化为秒,自定义的定时任务中,都是以秒为单位运行的* 时间表达式* 1:00 00 00 00-中间以空格分开*  :秒 分 时 日* 2:yyyy-MM-dd HH:mm:ss* 3:yyyy-MM-dd* 4.时间戳Timestamp* @AUTHOR : WuShukai1103* @TIME : 2018/1/24  23:21*/
public class TimeTransform {public static SimpleDateFormat day = new SimpleDateFormat("yyyy-MM-dd");public static SimpleDateFormat fullDay = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static long getTime(String expression) {if (Tool.getInstance().isNullOrEmpty(expression)) {throw new RuntimeException("不是正确的时间表达式");}String[] times = expression.split(" ");if (times.length > 4) {throw new RuntimeException("不是正确的时间表达式");}//最后总时间long start = 0;try {for (int i = 0; i < times.length; i++) {long time = Integer.parseInt(times[i]);switch (i) {//秒case 0:start += time;break;//分case 1:start += 60 * time;break;//时case 2:start += 60 * 60 * time;break;//日case 3:start += 60 * 60 * 24 * time;}}} catch (Exception e) {try {start = fullDay.parse(expression).getTime() - new Date().getTime();start = start > 0 ? start / 1000 : 0;} catch (ParseException e1) {try {start = day.parse(expression).getTime() - new Date().getTime();start = start > 0 ? start / 1000 : 0;} catch (ParseException e2) {throw new RuntimeException("不是正确的时间表达式");}}}return start;}public static long getTime(Date date) {//最后总时间long start;start = date.getTime() - new Date().getTime();start = start > 0 ? start / 1000 : 0;return start;}public static long getTime(long date) {long start;start = date - new Date().getTime();start = start > 0 ? start / 1000 : 0;return start;}public static long getTime(Timestamp date) {long start;start = date.getTime() - new Date().getTime();start = start > 0 ? start / 1000 : 0;return start;}
}复制代码

7. 创建MyTask运行类,该类是主要的线程运行核心

package com.wsk.movie.task;import com.wsk.movie.task.entity.MytaskEntity;
import com.wsk.movie.task.entity.MytaskerrorEntity;
import com.wsk.movie.task.entity.MytasklogEntity;
import com.wsk.movie.task.queue.MyQueue;
import com.wsk.movie.task.queue.MyQueueBean;
import com.wsk.movie.task.runnable.DelKeyRunnable;
import com.wsk.movie.task.runnable.MyRunnable;
import com.wsk.movie.task.service.MyErrorTaskRepository;
import com.wsk.movie.task.service.MyTaskLogRepository;
import com.wsk.movie.task.service.MyTaskRepository;
import com.wsk.movie.task.tool.TimeTransform;
import com.wsk.movie.tool.SpringContextUtil;import java.sql.Timestamp;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;/*** @DESCRIPTION :自定义定时器-单例* @AUTHOR : WuShukai1103* @TIME : 2018/1/23  22:22*/
public class MyTask implements Runnable{/*** 使用定时线程池* 根据CPU进行任务调度*/private static ScheduledExecutorService service = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());private static MyErrorTaskRepository errorTaskRepository = (MyErrorTaskRepository) SpringContextUtil.getBean(MyErrorTaskRepository.class);private static MyTaskLogRepository logRepository = (MyTaskLogRepository) SpringContextUtil.getBean(MyTaskLogRepository.class);private static MyTaskRepository repository = (MyTaskRepository) SpringContextUtil.getBean(MyTaskRepository.class);private MyTask() {}@Overridepublic void run() {execute();}private static class NestClass {private static final MyTask MY_TASK = new MyTask();}public static MyTask getInstance() {return NestClass.MY_TASK;}public void execute(MyQueue queue) {MyQueueBean bean;Date now = new Date();try {bean = queue.take();
//            System.out.println("run:" + bean.getEntity().getTaskname());} catch (InterruptedException e) {e.printStackTrace();MytaskerrorEntity entity = new MytaskerrorEntity();entity.setTaskname("");entity.setMsg("队列获取失败");entity.setRtime(new Timestamp(now.getTime()));entity.setClassname("");errorTaskRepository.save(entity);return;}MyRunnable runnable = bean.getRunnable();MytaskEntity entity = bean.getEntity();//更新数据库long next = TimeTransform.getTime(entity.getExpression());repository.updateTime(entity.getTaskname(), new Timestamp(now.getTime()), new Timestamp(now.getTime() + next * 1000));//开始定时任务service.scheduleAtFixedRate(runnable, TimeTransform.getTime(entity.getStarttime()), next, TimeUnit.SECONDS);//删除队列中的keyservice.schedule(new DelKeyRunnable(entity), next, TimeUnit.SECONDS);//日志MytasklogEntity log = new MytasklogEntity();log.setTaskname(entity.getTaskname());log.setClassname(entity.getClassname());log.setRtime(new Timestamp(now.getTime()));logRepository.save(log);System.out.println("队列剩余:" + MyQueue.getInstance().size());}//开始运行定时任务public void execute() {while (true) {System.out.println("开始定时任务,size:" + MyQueue.getInstance().size());execute(MyQueue.getInstance());}}//关闭线程public void shutdown() {service.shutdown();}}复制代码

8. 创建一个在Spring Boot启动的时候也启动定时器的任务

package com.wsk.movie.task;import com.wsk.movie.task.entity.MytaskEntity;
import com.wsk.movie.task.entity.MytaskerrorEntity;
import com.wsk.movie.task.queue.MyQueue;
import com.wsk.movie.task.queue.MyQueueBean;
import com.wsk.movie.task.runnable.MyRunnable;
import com.wsk.movie.task.service.MyErrorTaskRepository;
import com.wsk.movie.task.service.MyTaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;/*** @DESCRIPTION :随着程序的启动而启动运行,运行定时任务,每5秒查询一次数据库* @AUTHOR : WuShukai1103* @TIME : 2018/1/24  22:11*/
@Component
public class MainTask implements CommandLineRunner {private static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newScheduledThreadPool(2);private final MyTaskRepository repository;private final MyErrorTaskRepository errorTaskRepository;@Autowiredpublic MainTask(MyTaskRepository repository, MyErrorTaskRepository errorTaskRepository) {this.repository = repository;this.errorTaskRepository = errorTaskRepository;}@Overridepublic void run(String... strings) throws Exception {SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(() -> {List<MytaskEntity> entities = repository.starts();
//            System.out.println("查询数据库:" + TimeTransform.fullDay.format(new Date()) + ",size:" + entities.size());for (MytaskEntity e : entities) {Timestamp now = new Timestamp(new Date().getTime());MyRunnable runnable;try {//通过反射获取运行的类runnable = (MyRunnable) Class.forName(e.getClassname()).newInstance();//将runnable存储到队列中MyQueue.getInstance().offer(new MyQueueBean().setEntity(e).setRunnable(runnable));} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {ex.printStackTrace();MytaskerrorEntity entity = new MytaskerrorEntity();entity.setClassname(e.getClassname());entity.setMsg(ex.getMessage());entity.setRtime(now);entity.setTaskname(e.getTaskname());errorTaskRepository.save(entity);}}}, 0, 5, TimeUnit.SECONDS);Thread.sleep(1);//加载队列后,立即运行MyTaskSCHEDULED_EXECUTOR_SERVICE.execute(MyTask.getInstance());}
}复制代码

自此,一个自定义的定时任务就完成了。

自定义Java定时器(基于ScheduledExecutorService)相关推荐

  1. java定时器每一分钟执行一次_2行代码搞定一个定时器

    如何使用? 用法 1.需要定时执行的方法上加上@Scheduled注解,这个注解中可以指定定时执行的规则,稍后详细介绍. 2.Spring容器中使用@EnableScheduling开启定时任务的执行 ...

  2. java定时器实现的三种方式

    java定时器可以使用Thread线程实现,Timer,以及 1.Thread线程 需要用Thread创建线程,这里就不代码了 2.Timer public static void main(Stri ...

  3. kettle中java组件_kettle系列-[KettleUtil]kettle插件,类似kettle的自定义java类控件

    该kettle插件功能类似kettle现有的定义java类插件,自定java类插件主要是支持在kettle中直接编写java代码实现自定特殊功能,而本控件主要是将自定义代码转移到jar包,就是说自定义 ...

  4. MyBatis-学习笔记04【04.自定义Mybatis框架基于注解开发】

    Java后端 学习路线 笔记汇总表[黑马程序员] MyBatis-学习笔记01[01.Mybatis课程介绍及环境搭建][day01] MyBatis-学习笔记02[02.Mybatis入门案例] M ...

  5. java定时器_拾遗Timer定时器

    一 Timer 介绍 在开发中我们经常会遇到一些简单定时任务的需求,而不需要量级较重的定时任务就可以采取java定时器: java.util.Timer工具类中的Timer 是定时器,但定时任务写在j ...

  6. 矩阵累积相乘 java_累积:轻松自定义Java收集器

    矩阵累积相乘 java Accumulative是针对Collector<T, A, R>的中间累积类型A提出的接口Collector<T, A, R>以使定义自定义Java ...

  7. 累积:轻松自定义Java收集器

    Accumulative是针对Collector<T, A, R>的中间累积类型A提出的接口Collector<T, A, R>以使定义自定义Java Collector更加容 ...

  8. java 定时器框架_java定时器

    java定时器 什么是Java定时器? Java 定时器就是在给定的间隔时间执行自己的任务; Java实现定时器有以下几种: 通过Timer来实现定时任务 Timer 是来自 java.util.Ti ...

  9. kettle 教程(四):自定义 Java 代码

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/qqfo24/article/detai ...

最新文章

  1. 数人云CEO王璞:容器正成为软件交付的标准
  2. 手把手教你用 TensorFlow 实现文本分类(下)
  3. 一款基于 Spring Boot 开发 OA 开源产品
  4. 人还是很需要成就感的
  5. Linux_2.6字符设备驱动实例
  6. regini.exe使用方法
  7. 条件变量、pthread_cond_init
  8. logrotate 命令切换linux系统日志
  9. 手把手教你写ORM(七)
  10. 通俗易懂的USB协议详解(转)
  11. 网站歌曲播放器php,推荐漂亮的flash网页MP3音乐播放器
  12. 数据结构课程设计——逆波兰表达式的计算
  13. vue+draggable +jsPlumb 表格数据连线
  14. 浏览器自动转到外国服务器,通过HSTS实现浏览器自动跳转https(非服务器响应跳转)...
  15. 文件查重删除,继续完善及修改上篇内容
  16. 【人脸识别】基于Flask网页实现虚拟主播实验
  17. Fluent学习笔记
  18. redhat7 安装php,Linux/redhat7.3 安装php以及常见错误
  19. FizzBuzz与写代码的“一万”个细节
  20. 电子邮箱怎么写?电子邮箱格式选择,电子邮件注册哪个好?

热门文章

  1. 从零开始使用CodeArt实践最佳领域驱动开发(三)
  2. iOS-----线程同步与线程通信
  3. [学习OpenCV攻略][001][Ubuntu安装及配置]
  4. C/C++ -- Gui编程 -- Qt库的使用 -- 使用.ui文件
  5. impala的工作原理的详解(图文)
  6. 接口测试的时候如何生成随机数据进行测试
  7. oracle设置no null,Oracle 在not in中使用null的问题
  8. 程序员面试时自称字节跳动工作两年,被发现学历造假,结果蒙了
  9. 最全JavaScript基础总结~建议收藏
  10. java guava map_Guava - Map