JFinal使用技巧-动态管理任务调度

模仿Cron4jPlugin插件写的 , 直接上 石马

package com.momathink.common.kit;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import it.sauronsoftware.cron4j.Scheduler;

/**
 * Cron4j 工具
 *
 * @author dufuzhong 2018-06-05 10:38:29
 */
public class Cron4jKit {

	private static final Map<String, MyScheduler> MAP = new ConcurrentHashMap<>();

	/**
	 * 添加一个定时任务
	 * @param configName 任务名称
	 * @param cron       表达式调试任务
	 * @param daemon     表示调试线程是否设置为守护线程,默认值为 true,守护线程会在 tomcat 关闭时自动关闭
	 * @param runnable   执行的任务
	 * @return 
	 */
	public static MyScheduler put(String configName, String cron, boolean daemon, Runnable runnable) {
		MyScheduler scheduler = new MyScheduler(configName, cron, daemon, runnable);
		MAP.put(configName, scheduler);
		start(configName);
		return scheduler;
	}

	public static MyScheduler get(String configName) {
		return MAP.get(configName);
	}

	/**
	 * 启动指定任务
	 * @param configName
	 */
	public static void start(String configName) {
		final MyScheduler scheduler = get(configName);
		if (scheduler != null && !scheduler.getScheduler().isStarted()) {
			scheduler.getScheduler().start();
		}
	}

	/**
	 * 启动全部任务
	 */
	public static void start() {
		for (final String configName : MAP.keySet()) {
			start(configName);
		}
	}

	/**
	 * 任务停止
	 */
	public static void stop(String configName) {
		final MyScheduler scheduler = get(configName);
		if (scheduler != null && scheduler.getScheduler().isStarted()) {
			scheduler.getScheduler().stop();
		}
	}

	/**
	 * 任务全部停止
	 */
	public static void stop() {
		for (final String configName : MAP.keySet()) {
			stop(configName);
		}
	}

	/**
	 *  删除任务
	 * @param configName
	 */
	public static void remove(String configName) {
		stop(configName);
		MAP.remove(configName);
	}

	/**
	 * 删除全部任务
	 */
	public static void remove() {
		stop();
		MAP.clear();
	}

	/**
	 * 立即执行
	 * @param configName
	 */
	public static void run(String configName) {
		final MyScheduler scheduler = get(configName);
		if (scheduler != null) {
			scheduler.getRunnable().run();
		}
	}

	/**
	 * 立即执行全部任务
	 */
	public static void run() {
		for (final String configName : MAP.keySet()) {
			run(configName);
		}
	}
	
	/**
	 * 反射Runnable对象
	 * @param className
	 * @return
	 */
	public static Runnable newRunnable(String className) {
		try {
			Object obj = Class.forName(className).newInstance();
			if(obj instanceof Runnable) {
				return (Runnable) obj;
			}
			throw new IllegalStateException(className + "必须声明Runnable接口");
		} catch (Exception e) {
			throw new IllegalStateException(e);
		}
	}
	
	@Override
	public String toString() {
		return MAP.toString();
	}

	public static class MyScheduler {
		String configName;
		String cron;
		boolean daemon;
		Runnable runnable;
		Scheduler scheduler;

		public MyScheduler(String configName, String cron, boolean daemon, Runnable runnable) {
			if (configName == null) {
				throw new IllegalStateException("configName 不能为null");
			}
			this.configName = configName;
			if (cron == null) {
				throw new IllegalStateException("cron 不能为null");
			}
			this.cron = cron;
			this.daemon = daemon;
			if (runnable == null) {
				throw new IllegalStateException("runnable 不能为null");
			}
			this.runnable = runnable;
			this.scheduler = new Scheduler();
			scheduler.schedule(cron, runnable);
			scheduler.setDaemon(daemon);
		}

		public Scheduler getScheduler() {
			return scheduler;
		}

		public Runnable getRunnable() {
			return runnable;
		}

		@Override
		public String toString() {
			return "MyScheduler [configName=" + configName + ", cron=" + cron + ", daemon=" + daemon + ", runnable="
			        + runnable + ", scheduler=" + scheduler + "]";
		}
	}
	
	
	
	
        //测试:
	public static void main(String[] args) throws Exception {
		System.out.println("Cron4jKit: 开始装载每分钟执行一次的任务");
		final String configName = "main";
		
		Cron4jKit.put(configName, "* * * * *", true, () -> System.out.println("Cron4jKit: main 任务运行 一次"));
		//Cron4jKit.put(className, "* * * * *", true, Cron4jKit.newRunnable(className));
		
		Cron4jKit.run(configName);
		System.out.println("Cron4jKit: 装载完毕等待异步执行");
		Thread.sleep(121000);
		Cron4jKit.stop(configName);
		Cron4jKit.stop(configName);
		System.out.println("Cron4jKit: 完毕");
	}

}
[object Object]


评论区

lyh061619

2018-03-22 11:18

这个不错哦,用cron4j根本不用什么插件不插件的,就这样用就好了。^_^

杜福忠

2018-03-22 20:52

@lyh061619 javaweb 最牛逼的是 可以用 java

ztvip71

2018-03-25 14:54

动态调用Cron4jKit的stop()方法时,MAPCP为空,任务不停止。是什么原因?

杜福忠

2018-03-25 16:30

@ztvip71 贴码吧, 猜不出来, 你试试设置 scheduler.setDaemon(true);

凉凉凉凉凉

2018-04-16 16:05

强强强强强

lyh061619

2018-06-02 17:56

@杜福忠 建议你调整下上面的分享,在stop方法中加下这个:MAPCP.remove(name);否则在重复开启/关闭,会报异常的。

杜福忠

2018-06-04 11:23

@lyh061619
调stop时给人家删除掉, 不好吧? 我觉得可以增加一个remove方法.
重复开启/关闭, 会报异常 ?
这个有测试用例吗?
调用Scheduler时, 它自己有锁的synchronized (lock)
我这多线程乱调没发现问题在哪里了

lyh061619

2018-06-04 11:57

@杜福忠 直接贴码给你了, 把你的两个stop方法加下 MAPCP.remove(name),代码如下。
public static void stop(String name) {
Scheduler scheduler = get(name);
if (scheduler != null) {
scheduler.stop();
MAPCP.remove(name);
}
}

public static void stop() {
for (Map.Entry kv : MAPCP.entrySet()) {
kv.getValue().stop();
MAPCP.remove(kv.getKey());
}
}

杜福忠

2018-06-04 13:42

@lyh061619 还是那个问题, stop时为什么要remove掉?

"重复开启/关闭, 会报异常" 这个也只是调用Scheduler的stop或start啊, 如果重复开启/关闭, 会报异常, 那作者肯定早就修复了, 而且我也没发现有这个问题啊...

你那测试出这个问题的场景是什么啊?

lyh061619

2018-06-05 09:04

@杜福忠 你把计划任务,把动态参数放到库中,动态装载Cron4jKit.put()方法中的指令,接下来就是做个开关实时动态开启或关闭,异常问题就会出来了,为什么?
答:案例重现环境=数据库+tomcat+做界面ui动态人机控制计划任务;引起问题原因,是Cron4jKit,现在把scheduler,放到MAPCP,当你Cron4jKit.put()来回装载任务时,调用Cron4jKit.stop(String name)这个方法时,这个scheduler原则上讲已被kill,但Cron4jKit中做了一层包裹:MAPCP,所以MAPCP还存在着已经关闭计划任务的一具尸体留在里(注:当调用Cron4jKit.stop()方法时,相关计划任务已经关闭,但在MAPCP保存还存着scheduler对象的对象实例,最重点的这个被关闭实例对象中变量:started=false,当started为flase,在cron4j源码一这层已经我做状态的判断,当如started=false就抛出throw new IllegalStateException("Scheduler not started");这个异常出来。),这样在put()方法中,第一行调用Cron4jKit.stop(),如果不加MAPCP.remove(name);做处理,就会报上面提到异常,是否明白?如果还是不明白或未感知到的话,要是那就哪天你真发现了,再调整优化吧。

杜福忠

2018-06-05 10:51

@lyh061619 发现了, 现在修改了一下, 麻烦再帮忙看下,
修改如下:
1 去掉 Cron4jKit.put 里面调取stop, 保留了start调取
2 增加 Cron4jKit.remove方法, 并且里面有stop调取
3 Cron4jKit.start 和 Cron4jKit.stop 都增加 started 判断

界面ui动态人机控制计划任务时: 添加, 删除, 暂停, 恢复 , 这样是否满足场景了 ?

lyh061619

2018-06-05 11:23

@杜福忠 其实这按我前面给的方法处理就满足了,至于你现在的调整,回头有时间,抽空给你测试下。^_^

天朝子民

2020-11-07 14:48

@杜福忠 界面ui动态人机控制计划任务时,通常还有一个【立即执行】的按钮,如何实现呢?Cron4jKit中似乎没有封装。

杜福忠

2020-11-07 14:58

@天朝子民 立即执行 其实就是 一个 Controller+Action方法去调用和任务器执行的同一段代码。一般代码拆分细的话,会有个Service业务代码,Controller和任务调度代码都执行调用Service代码即可。

天朝子民

2020-11-07 15:14

@杜福忠 这样也是一种解决办法吧,但是有N多个定时任务,每个Controller就都得加上一个Action来调用【立即执行】的Service业务代码。能不能把放到Scheduler的Runnable取出来,手工触发下?这样更加简洁、优雅。

杜福忠

2020-11-07 15:52

@天朝子民 好建议,已更新文章内容,增加一个run方法

天朝子民

2020-11-07 16:19

@杜福忠 赞一个 速度快的一批