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布局(空布局) 一般容器都有 ...
随机推荐
- Eclipse 使用小结
代码智能提示 Java智能提示 Window -> Preferences -> Java -> Editor -> Content Assist -> Auto Act ...
- MPSOC之8——启动及错误处理
有了BOOT.BIN(fsbl+pmu+atl+uboot).uImage.uramdisk.image.gz,dtb文件,就可以启动了.把上述文件统统拷贝到SD卡,并设置开发板为SD卡启动. 0. ...
- 【java设计模式】【结构模式Structural Pattern】装饰模式Decorator Pattern
public class Client { public static void main(String[] args) { Component component=new ConcreteCompo ...
- Java之数据类型,变量赋值
Java中的基础数据类型(四类八种): 1.整数型 byte----使用byte关键字来定义byte型变量,可以一次定义多个变量并对其进行赋值,也可以不进行赋值.byte型是整型中所分配的内存空间是最 ...
- htpasswd 命令详解
htpasswd参数 -c 创建passwdfile.如果passwdfile 已经存在,那么它会重新写入并删去原有内容. -n 不更新passwordfile,直接显示密码 -m 使用MD5加密(默 ...
- 网口做trunk
首先发现这个服务器的两个网口对应的交换机端口 ailixin-asw2960>en ailixin-asw2960#terminal monitor 查看端口的状态 ailixin-asw296 ...
- go defer (go延迟函数)
go defer (go延迟函数) Go语言的defer算是一个语言的新特性,至少对比当今主流编程语言如此.根据GO LANGUAGE SPEC的说法: A "defer" sta ...
- LODOP打印控件示例
一.lodop打印预览效果图 LODOP.PRINT_SETUP();打印维护效果图 LODOP.PREVIEW();打印预览图 二.写在前面 最近项目用到了LODOP的套打,主要用到两个地方,一是物 ...
- 模板引擎(smarty)知识点总结II
今天咱们继续来学习smarty!!! 知识点1:对于三种变量 常量的引用 有哪三种变量?a.assign赋值 b.系统保留变量(包括:$smarty.get,$smarty.post,$smarty. ...
- Vue入门总结
技术栈:VUE:Vue-router:Vue-resource:Vue-cli: 项目:个人博客vue重构 一.vue-cli脚手架搭建项目结构 全局安装vue-cli: npm install vu ...