Java--高效的定时任务设计
相信你在日常的开发中肯定遇到过这种问题: 需要对实体类的状态信息进行管理,比如一定时间后修改它为XXX状态.
举个例子: 订单服务,当用户提交了订单后,如果在30分钟内没有支付,自动取消订单,这就是一个对状态的管理;
再举一个我实际开发的例子: 消息管道的例子,用户来拉取消息后,如果在30s内没有提交,那么修改他的订阅状态为:未订阅,这样其他的实例可以建立连接继续读取.
整理设计图:
核心就是: 一个Thread + 一个Queue;Thread不断从队列中取出数据, 如果队列中为空或者里边的任务没到期,则线程卡住wait(timeOut).
二 详细设计
先是简单的有状态的实体类:ConsumerInfoState,这个类的核心是状态(订阅到期时间),所以得有对状态的查询设置,查询距到期还要多久等等....
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | 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是复杂点的,核心思想就这些,额外的代码就删除了..
最后测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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
转发请注明出处: http://www.cnblogs.com/jycboy/p/8301538.html
如果您觉得阅读本文对您有帮助,请点一下�?推荐”按钮,您的“推荐�?将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利�?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架