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
转发请注明出处: http://www.cnblogs.com/jycboy/p/8301538.html
Java--高效的定时任务设计的更多相关文章
- 基于Java反射的定时任务设计
一.使用场景 1.不需要立即执行.立即得到结果返回. 2.如果执行失败.需要有失败补偿机制. 3.和业务代码解耦,适用于不同的务场景. 4.调用接口的入参.出参 统计,方便查询. 二.执行顺序 1.业 ...
- 转:二十七、Java图形化界面设计——容器(JFrame)
转:http://blog.csdn.net/liujun13579/article/details/7756729 二十七.Java图形化界面设计——容器(JFrame) 程序是为了方便用户使用的, ...
- Java中异常处理和设计
在程序设计中,进行异常处理是非常关键和重要的一部分.一个程序的异常处理框架的好坏直接影响到整个项目的代码质量以及后期维护成本和难度.试想一下,如果一个项目从头到尾没有考虑过异常处理,当程序出错从哪里寻 ...
- 二十七、Java图形化界面设计——容器(JFrame)
摘自http://blog.csdn.net/liujun13579/article/details/7756729 二十七.Java图形化界面设计--容器(JFrame) 程序是为了方便用户使用的, ...
- Java图形化界面设计——容器(JFrame)
Java图形化界面设计——容器(JFrame) 程序是为了方便用户使用的,因此实现图形化界面的程序编写是所有编程语言发展的必然趋势,在命令提示符下运行的程序可以让我们了解java程序的基本知识体系结构 ...
- 01 Java图形化界面设计——容器(JFrame)
程序是为了方便用户使用的,因此实现图形化界面的程序编写是所有编程语言发展的必然趋势,在命令提示符下运行的程序可以让我们了解java程序的基本知识体系结构,现在就进入java图形化界面编程. 一.Jav ...
- Java基础-继承-编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight。小车类Car是Vehicle的子类,其中包含的属性有载人数 loader。卡车类Truck是Car类的子类,其中包含的属性有载重量payload。每个 类都有构造方法和输出相关数据的方法。最后,写一个测试类来测试这些类的功 能。
#29.编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight.小车类Car是Vehicle的子类,其中包含的属性有载人数 loader.卡车类T ...
- Java高效读取大文件
1.概述 本教程将演示如何用Java高效地读取大文件.这篇文章是Baeldung (http://www.baeldung.com/) 上“Java——回归基础”系列教程的一部分. 2.在内存中读取 ...
- 三十二、Java图形化界面设计——布局管理器之CardLayout(卡片布局)
摘自 http://blog.csdn.net/liujun13579/article/details/7773945 三十二.Java图形化界面设计--布局管理器之CardLayout(卡片布局) ...
- 三十三、Java图形化界面设计——布局管理器之null布局(空布局)
摘自http://blog.csdn.net/liujun13579/article/details/7774267 三十三.Java图形化界面设计--布局管理器之null布局(空布局) 一般容器都有 ...
随机推荐
- Python 项目实践一(外星人入侵小游戏)第三篇
今天是圣诞节,公司放假一天,趁着有空,学习了一下午,多写一篇博客吧! 接着上节的继续学习, 一 重构:模块game_functions 在大型项目中,经常需要在添加新代码前重构既有代码.重构旨在简化既 ...
- mysql与服务器之间的编码问题
1.之前在练习一个java web的项目时,mysql的编码设置正确,服务器的编码也设置正确,但向mysql中存储数据时,就会出现乱码问题,后来发现是mysql与服务器之间的编码设置问题,以下是mys ...
- 比最差的API(ETW)更差的API(LTTng)是如何炼成的, 谈如何写一个好的接口
最近这几天在帮柠檬看她的APM系统要如何收集.Net运行时的各种事件, 这些事件包括线程开始, JIT执行, GC触发等等. .Net在windows上(NetFramework, CoreCLR)通 ...
- HTML基础教程-标题
HTML 标题 在 HTML 文档中,标题很重要. HTML 标题 标题(Heading)是通过 <h1> - <h6> 等标签进行定义的. <h1> 定义最大的标 ...
- Kafka+Storm写入Hbase和HDFS
1.Storm整合Kafka 使用Kafka作为数据源,起到缓冲的作用 // 配置Kafka订阅的Topic,以及zookeeper中数据节点目录和名字 String zks = KafkaPrope ...
- kvm 随笔
1. 查看kvm虚拟机状态 # virsh list --all 2. KVM虚拟机开机 # virsh start windows 3. KVM虚拟机关机或断电 (1) 关机 virsh关机 ...
- adb指令介绍
一.adb命令格式为:adb [-d|-e|-s <serialNumber>] <command> 1.adb devices :列出当前电脑所连接的所有安卓设备 2.adb ...
- CSS 水平居中/布局 垂直居中 (月经问题)
水平居中 如果它是一个行内元素 对其父元素使用 text-align:center 即可实现. <p style = " text-align:center; width:300px; ...
- 【WebGL】《WebGL编程指南》读书笔记——第5章
一.前言 终于到了第五章了,貌似开始越来越复杂了. 二.正文 Example1:使用一个缓冲区去赋值多个顶点数据(包含坐标及点大小) function initVerte ...
- Handwritten Parsers & Lexers in Go (翻译)
用go实现Parsers & Lexers 在当今网络应用和REST API的时代,编写解析器似乎是一种垂死的艺术.你可能会认为编写解析器是一个复杂的工作,只保留给编程语言设计师,但我想消除这 ...