java 时间轮算法_时间轮算法 简单学习

本文介绍了如何使用Java实现多层时间轮算法,用于高效地处理定时任务。时间轮分为日、时、分、秒、毫秒五层,每层时间轮有不同的长度。通过时间轮,可以解决传统定时器如Timer和ScheduledThreadPool的限制。文中提供了具体代码实现,包括时间轮的增加、任务添加、升级和取消等功能。
摘要由CSDN通过智能技术生成

时间轮

为什么要用时间轮实现

通常用于实现linux内核任务、游戏类的buf计时。

单个时间轮局限性:保存的任务数量少,不能超过当前时间轮。

多层时间轮,典型:日-时-分-秒

传统java实现定时:Timer,只能单线程,会阻塞; Executors.newScheduledThreadPoll, 使用的最小堆来实现,任务还是不能太多,添加时间复杂度为O(logn)

时间轮简单实现

1.实现日、时、分、秒、毫秒时间轮,设置Tick间隔,tick=100ms

2.创建对应的时间轮数组,ms=1000/tick,s=60,min=60,hour=24,day=365,数组中的每个元素类型为List

3.添加定时任务(不能添加到当前tick区间)

3.1 获取当前时间轮时间 curTime, 获取定时任务delayTime

3.2使用curTime+delayTime获得任务下一次执行的事件,并判断是否需要时间轮升级(ms->s->min->hour->day)

3.3将该定时任务添加到升级后时间轮的具体索引上

4.时间轮-one Tick增加

4.1 每一个tick间隔,将会进行一次one-tick增加,会触发新的tick间隔内的任务,如果该任务period大于0,则继续添加该定时任务,参考3

4.2 每一层的时间轮完成后都会进行,时间轮升级(例如59s,增加到下一轮,现获取下一轮min的所有任务,然后再更新新的min对应s的时间轮),升级后新的时间轮任务就会在具体的降级时间轮中进行定位添加;

时间轮算法实现-参考https://github.com/wolaiye1010/zdc-java-script/ 加了些注释,增加取消

public class TimeWheelService {

public static void main(String[] args) {

TimeWheelService timeWheelService = new TimeWheelService(3);

timeWheelService.schedule(()->{

for(int i=0;i<100;i++){

final int a=i;

timeWheelService.schedule(()-> System.out.println("^^^^^^buff-"+a),100,80);

}

},100,0);

timeWheelService.schedule(()->{

for(int i=0;i<100;i++){

final int a=i;

timeWheelService.schedule(()-> System.out.println("=====>debuff-"+a),100,100);

}

},100,0);

timeWheelService.schedule(()-> System.out.println(new Date()),10,1000);

}

private MultiTimeWheel timeWheel=new MultiTimeWheel();

private TimeWheelThread timeWheelThread=new TimeWheelThread();

//每轮的时间轮长度

private static final int TICK=10;

private static final int wheelIndexMillisecondLength=1000/TICK;

private static final int wheelIndexSecondLength=60;

private static final int wheelIndexMinuteLength=60;

private static final int wheelIndexHourLength=24;

private static final int wheelIndexDayLength=365;

//每一轮对应的所有ticks

private static final long wheelMillisecondAllTicks=1L;

//1s 10格

private static final long wheelSecondAllTicks=wheelMillisecondAllTicks*wheelIndexMillisecondLength;

//1min 600

private static final long wheelMinuteAllTicks=wheelSecondAllTicks*wheelIndexSecondLength;

//1h

private static final long wheelHourAllTicks=wheelMinuteAllTicks*wheelIndexMinuteLength;

//1day

private static final long wheelDayAllTicks=wheelHourAllTicks*wheelIndexHourLength;

//每一轮当前的索引,可以精确获取时间

private AtomicInteger wheelIndexMillisecond=new AtomicInteger(0);

private AtomicInteger wheelIndexSecond=new AtomicInteger(0);

private AtomicInteger wheelIndexMinute=new AtomicInteger(0);

private AtomicInteger wheelIndexHour=new AtomicInteger(0);

private AtomicInteger wheelIndexDay=new AtomicInteger(0);

//实际存储

private volatile Vector[] wheelMillisecond=new Vector[wheelIndexMillisecondLength];

private volatile Vector[] wheelSecond=new Vector[wheelIndexSecondLength];

private volatile Vector[] wheelMinute=new Vector[wheelIndexMinuteLength];

private volatile Vector[] wheelHour=new Vector[wheelIndexHourLength];

private volatile Vector[] wheelDay =new Vector[wheelIndexDayLength];

public void schedule(Runnable runnable,long delay,long period){

if(period0) throw new RuntimeException("不能使得间隔周期小于时间片TICK:"+TICK+" ms,间隔周期可以为0ms");

synchronized(this){

TimeWheelTask timeWheelTask = new TimeWheelTask(delay, period, runnable);

schedule(timeWheelTask);

}

}

public void schedule(TimeWheelTask timeWheelTask){

//delay 加上当前相对于具体时间单位的余数。

//处理当前是0分59s时加入了1分1秒后任务,会导致在1分1秒时候执行,因此如果延迟本身大于当前的一轮周期,则用延迟加上当前时间与本轮毫秒值的余数

//00:00:59 + 61 = 00:02:00,可知,需要先加上本轮余数

timeWheelTask.delay=timeWheelTask.delay+timeWheel.getWheelNowTime(timeWheelTask.delay);

timeWheel.addTaskToWheel(timeWheelTask.delay,timeWheelTask);

}

//真正执行定时任务的线程池

private ThreadPoolExecutor threadPoolExecutor;

public TimeWheelService(int coreSize){

this.threadPoolExecutor=new ThreadPoolExecutor(coreSize,coreSize,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue(10000));

timeWheelThread.start();

}

//轮子

class MultiTimeWheel{

/**

* 增加 one-tick,可能会触发每层轮,时间轮的升级

*/

public void incrTick() {

if(incIndex(TimeUnit.MILLISECONDS)){

return;

}

if(incIndex(TimeUnit.SECONDS)){

return;

}

if(incIndex(TimeUnit.MINUTES)){

return;

}

if(incIndex(TimeUnit.HOURS)){

return;

}

incIndex(TimeUnit.DAYS);

}

//增加一个tick,处理因为升级导致的新事件添加

private boolean incIndex(TimeUnit timeUnit){

long allTicksNext;

Vector[] vectorsNext;

AtomicInteger index;

AtomicInteger indexNext;

int wheelLength;

int wheelLengthNext;

switch(timeUnit){

case DAYS:

allTicksNext=0;

vectorsNext=null;

index=wheelIndexDay;

indexNext=null;

wheelLength=wheelIndexDayLength;

wheelLengthNext=0;

break;

case HOURS:

allTicksNext=wheelDayAllTicks;

vectorsNext=wheelDay;

index=wheelIndexHour;

indexNext=wheelIndexDay;

wheelLength=wheelIndexHourLength;

wheelLengthNext=wheelIndexDayLength;

break;

case MINUTES:

allTicksNext=wheelHourAllTicks;

vectorsNext=wheelHour;

index=wheelIndexMinute;

indexNext=wheelIndexHour;

wheelLength=wheelIndexMinuteLength;

wheelLengthNext=wheelIndexHourLength;

break;

case SECONDS:

allTicksNext=wheelMinuteAllTicks;

vectorsNext=wheelMinute;

index=wheelIndexSecond;

indexNext=wheelIndexMinute;

wheelLength=wheelIndexSecondLength;

wheelLengthNext=wheelIndexMinuteLength;

break;

case MILLISECONDS:

allTicksNext=wheelSecondAllTicks;

vectorsNext=wheelSecond;

index=wheelIndexMillisecond;

indexNext=wheelIndexSecond;

wheelLength=wheelIndexMillisecondLength;

wheelLengthNext=wheelIndexSecondLength;

break;

default:

throw new RuntimeException("Timeunit 参数错误");

}

index.getAndIncrement();

if(index.get()

return true;

}

index.set(index.get()%wheelLength);

//如果是天数,因为当处理hours时候已经处理过天了,所以直接返回。

if(timeUnit.equals(TimeUnit.DAYS)){

return true;

}

//获取下一个时间轮的任务,并添加

List taskList = vectorsNext[(indexNext.get() + 1) % wheelLengthNext];

if(null!=taskList){

for(TimeWheelTask task:taskList){

addTaskToWheel(task.delay%(allTicksNext),task);

}

taskList.clear();

}

return false;

}

public List getTaskList() {

return wheelMillisecond[wheelIndexMillisecond.get()];

}

//加入时间轮,判断是否需要升级

void addTaskToWheel(long delay,TimeWheelTask task){

if(delay>=wheelIndexDayLength*wheelDayAllTicks*TICK){

throw new RuntimeException("不能超过一年");

}

if(addTaskToWheel(delay,task,TimeUnit.DAYS)){

return;

}

if(addTaskToWheel(delay,task,TimeUnit.HOURS)){

return;

}

if(addTaskToWheel(delay,task,TimeUnit.MINUTES)){

return;

}

if(addTaskToWheel(delay,task,TimeUnit.SECONDS)){

return;

}

addTaskToWheel(delay,task,TimeUnit.MILLISECONDS);

}

//添加任务到时间轮,

private boolean addTaskToWheel(long delay, TimeWheelTask timeWheelTask, TimeUnit timeUnit){

long allTicks;

Vector[] vectors;

AtomicInteger index;

int wheelLength;

switch (timeUnit){

case DAYS:

allTicks=wheelDayAllTicks;

vectors= wheelDay;

index=wheelIndexDay;

wheelLength=wheelIndexDayLength;

break;

case HOURS:

allTicks=wheelHourAllTicks;

vectors=wheelHour;

index=wheelIndexHour;

wheelLength=wheelIndexHourLength;

break;

case MINUTES:

allTicks=wheelMinuteAllTicks;

vectors=wheelMinute;

index=wheelIndexMinute;

wheelLength=wheelIndexMinuteLength;

break;

case SECONDS:

allTicks=wheelSecondAllTicks;

vectors=wheelSecond;

index=wheelIndexSecond;

wheelLength=wheelIndexSecondLength;

break;

case MILLISECONDS:

allTicks=wheelMillisecondAllTicks;

vectors=wheelMillisecond;

index=wheelIndexMillisecond;

wheelLength=wheelIndexMillisecondLength;

break;

default:

throw new RuntimeException("timeUnit 参数错误");

}

//添加到当前的索引

if(0!=delay/(allTicks*TICK) || timeUnit.equals(TimeUnit.MILLISECONDS)){

int indexNew=(index.get()+(int)(delay/(allTicks*TICK)))%wheelLength;

if(null==vectors[indexNew]){

vectors[indexNew]=new Vector();

}

vectors[indexNew].add(timeWheelTask);

return true;

}

return false;

}

//准确获取当前需要添加的准确时间轮

long getWheelNowTime(long delay){

//当前时间 毫秒

long timeFromWheelStart=(wheelIndexDay.get()*wheelDayAllTicks+wheelIndexHour.get()*wheelHourAllTicks+wheelIndexMinute.get()*wheelMinuteAllTicks

+wheelIndexSecond.get()*wheelSecondAllTicks+wheelIndexMillisecond.get()*wheelMillisecondAllTicks)*TICK;

//从大到小开始处理,是否大于1天

if(0!=delay/(wheelDayAllTicks*TICK)){

return timeFromWheelStart%(wheelDayAllTicks*TICK);

}

//大于1小时

if(0!=delay/(wheelHourAllTicks*TICK)){

return timeFromWheelStart%(wheelHourAllTicks*TICK);

}

//大于1分钟

if(0!=delay/(wheelMinuteAllTicks*TICK)){

return timeFromWheelStart%(wheelMinuteAllTicks*TICK);

}

//大于1秒

if(0!=delay/(wheelSecondAllTicks*TICK)){

return timeFromWheelStart%(wheelSecondAllTicks*TICK);

}

return 0;

}

}

/**

* 时间轮的task

*/

class TimeWheelTask implements Runnable{

private long delay;

//-1 为手动取消、0为1次定时、大于0表示正常调度间隔

private long period;

private Runnable runnable;

public void setDelay(long delay) {

this.delay = delay;

}

TimeWheelTask(long delay, long period, Runnable runnable){

this.delay=delay;

this.period=period;

this.runnable=runnable;

}

/**

* 判断是否是周期性的调度任务

* @return

*/

public boolean isPeriodSchedule(){

return period>0;

}

@Override

public void run() {

if(this.period==-1){

return;

}

runnable.run();

}

/**

* 手动消除调度

*/

public void cancel(){

this.period=-1;

}

}

//时间轮 main线程

class TimeWheelThread extends Thread{

public TimeWheelThread(){

super("TimeWheel_main");

}

public TimeWheelThread(String thread_name){

super(thread_name);

}

@Override

public void run() {

try {

mainLoop();

}catch (Exception e){

System.out.println(e);

}finally {

System.out.println(111);

}

}

private void mainLoop() {

while (true){

//运行任务

runTask(timeWheel.getTaskList());

//时间增加 one-tick

timeWheel.incrTick();

//休眠

try {

TimeUnit.MILLISECONDS.sleep(TICK);

} catch (InterruptedException e) {

e.printStackTrace();

throw new RuntimeException(e);

}

}

}

private void runTask(List taskList) {

if(taskList==null || taskList.size()==0) return;

for(TimeWheelTask timeWheelTask:taskList){

threadPoolExecutor.execute(timeWheelTask);

if(timeWheelTask.isPeriodSchedule()){

timeWheelTask.setDelay(timeWheelTask.period);

schedule(timeWheelTask);

}

}

taskList.clear();

}

}

}

角色

Bucket:槽位:链式环形存储任务

Timer:主循环:推进时间轮、持有工作线程池(实际执行任务的)、主循环线程池

Timewheel: 时间轮:(持有父级时间轮引用),添加任务、时间Tick(间隔)、持有Bucket size,当前时间

TimeTask: 运行任务:作为Bucket 持有的任务引用、失效时间、取消状态等,

创建DelayQueue, TimeWheel以及附属(上级)时间轮持有一个queue

可以实现对时间轮中的槽位进行改造,实现槽内任务链式存储、槽位本身实现Delayed接口,配合DelayQueue实现时间轮推进

每一个任务添加时,对应的槽位判断是否添加到queue,如果时间超时超出当前时间轮范围,则获取上级时间轮(如果不存在)则创建。

时间轮推进则依靠queue.poll(timeout),如果有最近到期任务,就通过timewheel推进时间轮。

使用ScheduledThreadPoll

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

ScheduledFuture> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {

System.out.println(new Date());

}, 10, 20, TimeUnit.SECONDS);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值