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

posted @ 2018-01-17 10:37  超超boy  阅读(7437)  评论(0编辑  收藏  举报