环形队列高效触发大量超时任务的算法实现

基于环形队列的超时触发算法只需要一个timer即可实现批量超时任务的触发,CPU消耗低,效率高。下面是此算法的简单实现。

1,TaskHolder.java

package com.zws.timer;
/**
 * 
 * @author wensh.zhu
 * @date 2018-04-22
 */
public class TaskHolder {

	/** 任务所需等待的圈数,即任务需要走几圈**/
	private int cycles;
	private int delays;
	private Runnable task;

	public TaskHolder() {}

	public TaskHolder(int cycles, int delays, Runnable task) {
		this.cycles = cycles;
		this.delays = delays;
		this.task = task;
	}

	public boolean isTimeOut() {
		return cycles <= 0;
	}

	public void cutDown() {
		cycles --;
	}

	public int getCycles() {
		return cycles;
	}

	public void setCycles(int cycles) {
		this.cycles = cycles;
	}

	public int getDelays() {
		return delays;
	}

	public void setDelays(int delays) {
		this.delays = delays;
	}

	public Runnable getTask() {
		return task;
	}

	public void setTask(Runnable task) {
		this.task = task;
	}

	@Override
	public String toString() {
		return "TaskHolder[cycles=" + cycles + ", delays=" + delays + "]";
	}

}

2,TimerContext.java

package com.zws.timer;

import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
 * 
 * @author wensh.zhu
 * @date 2018-04-22
 */
public class TimerContext {

	public static final int DEFAULT_TICKS = 60;
	public static final int DEFAULT_TICK_DURATION = 1;

	private Map<Integer, Queue<TaskHolder>> taskHolders;
	private volatile int currentTick = 0;

	/** tick一圈的长度 **/
	private int ticks = DEFAULT_TICKS;

	/** 每tick一次的时间间隔,单位:秒**/
	private int tickDuration = DEFAULT_TICK_DURATION;

	public TimerContext() {
		init();
	}

	public TimerContext(int ticks, int tickDuration) {
		if (ticks <= 0)
			throw new IllegalArgumentException("ticks must be greater than 0");

		if (tickDuration <= 0)
			throw new IllegalArgumentException("tickDuration must be greater than 0");

		this.ticks = ticks;
		this.tickDuration = tickDuration;
		init();
	}

	private void init() {
		taskHolders = new ConcurrentHashMap<Integer, Queue<TaskHolder>>();
		for (int i = 0; i < ticks; i ++)
			taskHolders.put(i, new ConcurrentLinkedQueue<TaskHolder>());
	}

	/**
	 * 添加一个定时任务并计算需要走的圈数和落脚的index
	 * @param task
	 * @param delays
	 */
	public void addTask(Runnable task, int delays) {
		if (task == null) 
			throw new NullPointerException("task must not be null");

		if (delays <=0) 
			throw new IllegalArgumentException("delays must be greater than 0");

		int allSeconds = ticks * tickDuration;
		int cycles = delays / allSeconds;
		int index = ((delays % allSeconds) / tickDuration) + currentTick;
		TaskHolder metaData = new TaskHolder(cycles, delays, task);
		taskHolders.get(index).add(metaData);
	}

	public int tick() {
		currentTick = (currentTick + 1) % ticks;
		return currentTick;
	}

	public Queue<TaskHolder> getCurrentTasks() {
		return taskHolders.get(currentTick);
	}

	public int getCurrentTick() {
		return currentTick;
	}

	public int getTicks() {
		return ticks;
	}

	public int getTickDuration() {
		return tickDuration;
	}

	@Override
	public String toString() {
		return "TimerContext [timers=" + taskHolders + ", ticks=" + ticks + ", tickDuration=" + tickDuration
				+ ", currentTick=" + currentTick + "]";
	}
}

3,TimerScheduler.java

package com.zws.timer;

import java.io.IOException;
import java.util.Iterator;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
/**
 * 用于判断定时器是否到时、执行任务、维护定时器状态。
 * @author wensh.zhu
 * @date 2018-04-22
 */
public class TimerScheduler extends TimerTask {

	private TimerContext timerContext;

	public TimerScheduler() {}

	public TimerScheduler(TimerContext timerContext) {
		this.timerContext = timerContext;
	}

	/**
	 * 定时检测,如果定时器触发时间到了就从集合中删除并执行任务,否则圈数减一。
	 */
	@Override
	public void run() {
		if (timerContext == null) 
			return;

		Queue<TaskHolder> timers = timerContext.getCurrentTasks();
		Iterator<TaskHolder> itor = timers.iterator();
		while (itor.hasNext()) {
			TaskHolder timer = itor.next();
			if (timer.isTimeOut()) {
				itor.remove();
				new Thread(timer.getTask()).start();
			} else {
				timer.cutDown();
			}
		}

		timerContext.tick();
	}

	public void addTask(Runnable task, int delays) {
		timerContext.addTask(task, delays);
	}

	public TimerContext getTimerContext() {
		return timerContext;
	}

	public void setTimerContext(TimerContext timerContext) {
		this.timerContext = timerContext;
	}

	public static void main(String[] args) throws IOException {
		TimerContext context = new TimerContext(60, 1);
		TimerScheduler sheduler = new TimerScheduler(context);
		sheduler.addTask(new Runnable() {

			public void run() {
				System.out.println(DateUtils.now());
			}
		}, 60);
		System.out.println(DateUtils.now());

		Timer timer = new Timer();
		timer.scheduleAtFixedRate(sheduler, 0, context.getTickDuration() * 1000L);

		System.in.read();
	}

}

4,DateUtils.java

package com.zws.timer;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * 
 * @author wensh.zhu
 * @date 2018-04-22
 */
public class DateUtils {
	public static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss";

	public static String now() {
		LocalDateTime time = LocalDateTime.now();
		return time.format(DateTimeFormatter.ofPattern(DEFAULT_PATTERN));
	}

	public static String plusSeconds(int seconds) {
		LocalDateTime time = LocalDateTime.now();
		time.plusSeconds(seconds);
		return time.format(DateTimeFormatter.ofPattern(DEFAULT_PATTERN));
	}
}

原文地址:http://blog.51cto.com/wenshengzhu/2106623

时间: 2024-10-31 05:36:54

环形队列高效触发大量超时任务的算法实现的相关文章

10w定时任务,如何高效触发超时

一.缘起 很多时候,业务有定时任务或者定时超时的需求,当任务量很大时,可能需要维护大量的timer,或者进行低效的扫描. 例如:58到家APP实时消息通道系统,对每个用户会维护一个APP到服务器的TCP连接,用来实时收发消息,对这个TCP连接,有这样一个需求:"如果连续30s没有请求包(例如登录,消息,keepalive包),服务端就要将这个用户的状态置为离线". 其中,单机TCP同时在线量约在10w级别,keepalive请求包大概30s一次,吞吐量约在3000qps. 一般来说怎么

管理大量定时任务,如果高效触发超时?

1. 背景 很多时候,业务有定时任务或定时超时的需求,当任务量很大时,可能需要维护大量的timer,或者进行低效的扫描. 例如:对每个用户会维护一个APP到服务器的TCP连接,用来实时收发信息,对这个TCP连接,如果连续30s没有请求包,服务端就要将这个连接断开. 一般说怎么实现这类需求呢? 2. 一般思路 2.1 轮询扫描法 (1)用一个Map<uid, last_packet_time>来记录每一个uid最近一次请求时间last_packet_time: (2)当某个用户uid有请求包来到

&lt;2014 05 16&gt; 线性表、栈与队列——一个环形队列的C语言实现

栈与队列都是具有特殊存取方式的线性表,栈属于先进后出(FILO),而队列则是先进先出(FIFO).栈能够将递归问题转化为非递归问题,这是它的一个重要特性.除了FILO.FIFO这样的最普遍存取方式外,还有一些扩展的数据结构,如双端队列.双栈.超队列.超栈等,它们是一种扩展与变异结构. 线性表有顺序存储和链接存储两类,这是针对计算机的线性存储空间作出的分类.前者可以是数组,后者可以是链表.字符串是线性表最常见的应用. 这里我用C语言实现了一个基于数组环形队列,它具有固定的队列空间.相比于链表实现,

【转】环形队列理论

原文链接:http://blog.sina.com.cn/s/blog_8b200d440100xsug.html 环形队列是在实际编程极为有用的数据结构,它有如下特点. 它是一个首尾相连的FIFO的数据结构,采用数组的线性空间,数据组织简单.能很快知道队列是否满为空.能以很快速度的来存取数据. 因为有简单高效的原因,甚至在硬件都实现了环形队列. 环形队列广泛用于网络数据收发,和不同程序间数据交换(比如内核与应用程序大量交换数据,从硬件接收大量数据)均使用了环形队列. 一.环形队列实现原理 --

Atitit.提升软件稳定性---基于数据库实现的持久化 循环队列 环形队列

Atitit.提升软件稳定性---基于数据库实现的持久化  循环队列 环形队列 1. 前言::选型(马) 1 2. 实现java.util.queue接口 1 3. 当前指针的2个实现方式 1 1.1. 用一个游标last 来指示 (指针表字段last ),麻烦的,不推荐 1 1.2. (简单,推荐)使用循环次数来指示,每循环加1   (字段cirTimes),order by cirtimes 1 4. 表格设计id, cirTimes,createtime,handlerID,recID,d

环形缓冲区的设计及其在生产者消费者模式下的使用(并发有锁环形队列)

1.环形缓冲区 缓冲区的好处,就是空间换时间和协调快慢线程.缓冲区可以用很多设计法,这里说一下环形缓冲区的几种设计方案,可以看成是几种环形缓冲区的模式.设计环形缓冲区涉及到几个点,一是超出缓冲区大小的的索引如何处理,二是如何表示缓冲区满和缓冲区空,三是如何入队.出队,四是缓冲区中数据长度如何计算. ps.规定以下所有方案,在缓冲区满时不可再写入数据,缓冲区空时不能读数据 1.1.常规数组环形缓冲区 设缓冲区大小为N,队头out,队尾in,out.in均是下标表示: 初始时,in=out=0 队头

并发无锁之环形队列生产者消费者问题

1.生产者消费者问题 三种关系:生产者--生产者(互斥):消费者-消费者(互斥):生产者--消费者(互斥同步) 两个角色:生产者:消费者 一种生产场所:缓冲区 2.环形队列(缓冲区) 数据结构:可以有多种,这里选用数组,逻辑上将a[0]和a[size-1]相连构成环形队列 判断空/判断满:当读指针和写指针指向同一个区域为空,或者满(但不能区分空或者满) 两种方案:1.即尾结点与首结点之间至少留有一个元素的空间. 2. 添加一个计数器(信号量就是计数器所以这里用信号量完成计数器的功能) 3.sem

环形队列的实现原理

环形队列是一个首尾相连的FIFO(命名管道)的数据结构,它采用数组的线性空间.它能很快知道队列是否为满或者为空,也能很快的存取数据. 原理: 内存上没有环形结构,因此环形队列利用数组的线性空间来实现.当数据到了尾部时,它将转回到0位置来处理.这个转回操作通过取模来执行. 构造:逻辑上,将数组q[0]与q[MAXN-1]相连接,形成一个存放队列的环形空间. 用数组下标来标明队列的读.写位置.head 指向可以读的位置,tail 指向可以写的位置. 环形队列的关键是判断队列为空或者满.tail 追上

java实现数据结构中的环形队列

最近在看数据结构,队列在数据结构中是个重要的元素. 定义:数据结构是指相互之间存在一种或多种特定关系的数据元素的集合. 队列主要分为普通队列和环形队列,环形队列比普通队列的使用效率更高(普通队列容易造成内存的浪费,时间效率也会降低,主要体现在队列的删除操作上) 下面用java来实现队列,仅供参考 package demo; //环形队列 public class QueueDemo { //创建队列 public QueueDemo(int num){ m_iQueueCapacity=num;