Java--高效的定时任务设计
相信你在日常的开发中肯定遇到过这种问题: 需要对实体类的状态信息进行管理,比如一定时间后修改它为XXX状态.
举个例子: 订单服务,当用户提交了订单后,如果在30分钟内没有支付,自动取消订单,这就是一个对状态的管理;
再举一个我实际开发的例子: 消息管道的例子,用户来拉取消息后,如果在30s内没有提交,那么修改他的订阅状态为:未订阅,这样其他的实例可以建立连接继续读取.
整理设计图:
核心就是: 一个Thread + 一个Queue;Thread不断从队列中取出数据, 如果队列中为空或者里边的任务没到期,则线程卡住wait(timeOut).
二 详细设计
先是简单的有状态的实体类:ConsumerInfoState,这个类的核心是状态(订阅到期时间),所以得有对状态的查询设置,查询距到期还要多久等等....
import java.io.Serializable; public class ConsumerInfoState implements Serializable { /** * 序列化ID */ private static final long serialVersionUID = 1L; /** * 过期时间20s */ protected long expiration; private String topic; private String userId; private boolean isSubscribed = false; private long CONSUMER_INSTANCE_TIMEOUT_MS_DEFAULT = 5000; public ConsumerInfoState(String userId) { this.userId = userId; this.expiration = System.currentTimeMillis() + CONSUMER_INSTANCE_TIMEOUT_MS_DEFAULT; } public ConsumerInfoState(String topic, String userId) { super(); this.topic = topic; this.userId = userId; this.expiration = System.currentTimeMillis() + CONSUMER_INSTANCE_TIMEOUT_MS_DEFAULT; } /** *是否过期 */ public boolean expired(long nowMs) { return expiration <= nowMs; } /** * <p> * 更新订阅过期时间 * </p> */ public void updateExpiration() { this.expiration = System.currentTimeMillis() + CONSUMER_INSTANCE_TIMEOUT_MS_DEFAULT; } /** * <p> * 到指定时间还有多久 * </p> */ public long untilExpiration(long nowMs) { return this.expiration - nowMs; } public String getUserId() { return userId; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public void setSubscribed(boolean isSubscribed) { this.isSubscribed = isSubscribed; } public boolean hasSubscribed() { return isSubscribed; } }
这个类还是很清晰的..
核心类: ConsumerInfoManager
import java.util.Comparator; import java.util.PriorityQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ConsumerInfoManager { Logger logger = LoggerFactory.getLogger(ConsumerInfoManager.class); //任务队列 private final PriorityQueue<ConsumerInfoState> consumersByExpiration = new PriorityQueue<ConsumerInfoState>( new Comparator<ConsumerInfoState>() { //小的在前 public int compare(ConsumerInfoState o1, ConsumerInfoState o2) { if (o1.expiration < o2.expiration) { return -1; } else if (o1.expiration == o2.expiration) { return 0; } else { return 1; } } }); private ExpirationThread expirationThread; public ConsumerInfoManager() { //启动线程 this.expirationThread = new ExpirationThread(); this.expirationThread.start(); } //加入任务队列 public synchronized void addConsumerInfoSate(ConsumerInfoState consumerInfoSate) { consumersByExpiration.add(consumerInfoSate); this.notifyAll(); } @SuppressWarnings("unused") public synchronized void updateExpiration(ConsumerInfoState state) { // 先删除在放里边,重新排序 consumersByExpiration.remove(state); state.updateExpiration(); consumersByExpiration.add(state); this.notifyAll(); } public void shutdown() { logger.debug("Shutting down consumers"); expirationThread.shutdown(); synchronized (this) { consumersByExpiration.clear(); } } /** * <p> * 检查consumerInfo的过期时间,过期就从缓存中删除 * </p> * @author jiangyuechao 2018年1月13日 下午2:04:30 */ @SuppressWarnings("unused") private class ExpirationThread extends Thread { AtomicBoolean isRunning = new AtomicBoolean(true); CountDownLatch shutdownLatch = new CountDownLatch(1); public ExpirationThread() { super("Consumer Expiration Thread"); setDaemon(true); } @Override public void run() { synchronized (ConsumerInfoManager.this) { try { while (isRunning.get()) { long now = System.currentTimeMillis(); //队列空和最近一个任务是否到期的判断 while (!consumersByExpiration.isEmpty() && consumersByExpiration.peek().expired(now)) { final ConsumerInfoState state = consumersByExpiration.remove(); //{你自己的业务处理} state.setSubscribed(false); logger.info("任务已到期,topic:{}, userID:{},subscribed:{}",state.getTopic(),state.getUserId(),state.hasSubscribed()); } //需要等待的时间 long timeout = consumersByExpiration.isEmpty() ? Long.MAX_VALUE : consumersByExpiration.peek().untilExpiration(now); ConsumerInfoManager.this.wait(timeout); } } catch (InterruptedException e) { // Interrupted by other thread, do nothing to allow this thread to exit logger.error("ExpirationThread线程中断", e); } } shutdownLatch.countDown(); } public void shutdown() { try { isRunning.set(false); this.interrupt(); shutdownLatch.await(); } catch (InterruptedException e) { throw new Error("Interrupted when shutting down consumer worker thread."); } } } public void join(){ try { expirationThread.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
代码就这些,我进行了删减,删除了不重要的部分, 一般ConsumerInfoManager还需要一个缓存Cache,Cache中是存储所有的实体类,queue中是Cache中的一部分,一般queue中的任务到期,需要从Cache中删除或取出执行一些操作.
当然加Cache是复杂点的,核心思想就这些,额外的代码就删除了..
最后测试一下
public class ManagerTest { static ConsumerInfoManager consumerInfoManager; static String userId = "dhsajkdsajkdsjh1"; static Logger logger = LoggerFactory.getLogger(ManagerTest.class); public static void main(String[] args) throws InterruptedException { //实例化 setUp(); for(int i = 0;i<3;i++){ ConsumerInfoState consumerInfoState = new ConsumerInfoState("chao-"+i, userId); consumerInfoState.setSubscribed(true); consumerInfoManager.addConsumerInfoSate(consumerInfoState); logger.info("任务"+i+"加入队列"); Thread.sleep(1000); } consumerInfoManager.join(); } public static void setUp(){ consumerInfoManager = new ConsumerInfoManager(); } }
输出结果: 符合预期...
2018-01-17 10:07:27,450 [main] INFO ManagerTest - 任务0加入队列
2018-01-17 10:07:28,451 [main] INFO ManagerTest - 任务1加入队列
2018-01-17 10:07:29,451 [main] INFO ManagerTest - 任务2加入队列
2018-01-17 10:07:32,451 [Consumer Expiration Thread] INFO ConsumerInfoManager - 任务已到期,topic:chao-0, userID:dhsajkdsajkdsjh1,subscribed:false
2018-01-17 10:07:33,485 [Consumer Expiration Thread] INFO ConsumerInfoManager - 任务已到期,topic:chao-1, userID:dhsajkdsajkdsjh1,subscribed:false
2018-01-17 10:07:34,452 [Consumer Expiration Thread] INFO ConsumerInfoManager - 任务已到期,topic:chao-2, userID:dhsajkdsajkdsjh1,subscribed:false