JAVA实现延时过期MAP 支持自定义过期触发事件

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA实现延时过期MAP 支持自定义过期触发事件相关的知识,希望对你有一定的参考价值。

如题,直接上代码:

  1 import java.util.Iterator;
  2 import java.util.concurrent.ConcurrentHashMap;
  3 import java.util.concurrent.TimeUnit;
  4 
  5 import org.slf4j.Logger;
  6 import org.slf4j.LoggerFactory;
  7 
  8 /**
  9  * 实现延时过期MAP集合 支持自定义过期触发事件
 10  * 
 11  * @ClassName: BaseExpireMap
 12  * @Description: TODO
 13  * @author: wangs
 14  * @date: 2017-12-25 上午9:59:04
 15  * @param <K>
 16  * @param <V>
 17  */
 18 public abstract class BaseExpireMap<K, V> {
 19     protected static final Logger logger = LoggerFactory.getLogger(BaseExpireMap.class);
 20     private long expTime = 0L;
 21     private TimeUnit unit = null;
 22     /**
 23      * 线程安全的map容器
 24      */
 25     ConcurrentHashMap<K, V> expireMap = null;
 26     /**
 27      * 控制过期时间
 28      */
 29     ConcurrentHashMap<K, Long> delayMap = null;
 30     
 31     /**
 32      * 将map提供给外部程序操作
 33      * @Title: getDataMap 
 34      * @Description: TODO
 35      * @return
 36      * @return: ConcurrentHashMap<K,V>
 37      */
 38     public ConcurrentHashMap<K, V> getDataMap(){
 39         return this.expireMap;
 40     }
 41 
 42     public BaseExpireMap(long expTime, TimeUnit unit) {
 43         expireMap = new ConcurrentHashMap<K, V>();
 44         delayMap = new ConcurrentHashMap<K, Long>();
 45         this.expTime = expTime;
 46         this.unit = unit;
 47         // 启动监听线程
 48         BaseExpireCheckTask task = new BaseExpireCheckTask(expireMap, delayMap) {
 49             @Override
 50             protected void expireEvent(K key,V val) {
 51                 baseExpireEvent(key,val);
 52             }
 53         };
 54         task.setDaemon(false);
 55         task.start();
 56     }
 57 
 58     /**
 59      * 过期事件 子类实现
 60      * 
 61      * @Title: baseExpireEvent
 62      * @Description: TODO
 63      * @param key
 64      * @return: void
 65      */
 66     protected abstract void baseExpireEvent(K key,V val);
 67 
 68     public V put(K key, V val) {
 69         delayMap.put(key, getExpireTime());
 70         return expireMap.put(key, val);
 71     }
 72 
 73     public V remove(K key) {
 74         return expireMap.remove(key);
 75     }
 76     
 77     public V get(K key){
 78         return expireMap.get(key);
 79     }
 80 
 81     private Long getExpireTime() {
 82         return unit.toMillis(expTime) + System.currentTimeMillis();
 83     }
 84 
 85     public static void main(String[] args) {
 86         System.out.println(TimeUnit.SECONDS.toMinutes(120));
 87         System.out.println(TimeUnit.MICROSECONDS.toMillis(120));
 88         System.out.println(TimeUnit.MILLISECONDS.toMillis(120));
 89     }
 90 
 91     /**
 92      * 扫描线程 定期移除过期元素并触发过期事件
 93      * 
 94      * @ClassName: BaseExpireCheckTask
 95      * @Description: TODO
 96      * @author: wangs
 97      * @date: 2017-12-25 上午9:59:18
 98      */
 99     private abstract class BaseExpireCheckTask extends Thread {
100         ConcurrentHashMap<K, Long> delayMap = null;
101         ConcurrentHashMap<K, V> expireMap = null;
102 
103         public BaseExpireCheckTask(ConcurrentHashMap<K, V> expireMap, ConcurrentHashMap<K, Long> delayMap) {
104             this.delayMap = delayMap;
105             this.expireMap = expireMap;
106         }
107 
108         protected abstract void expireEvent(K key,V val);
109 
110         public void run() {
111             Iterator<K> it = null;
112             K key = null;
113             while (true) {
114                 if (delayMap != null && !delayMap.isEmpty()) {
115                     it = delayMap.keySet().iterator();
116                     while (it.hasNext()) {
117                         key = it.next();
118                         if (delayMap.get(key) <= System.currentTimeMillis()) {// 元素超时
119                             // 触发回调
120                             expireEvent(key,expireMap.get(key));
121                             // 移除
122                             it.remove();
123                             expireMap.remove(key);
124                             delayMap.remove(key);
125                         }
126                     }
127                 }
128                 try {
129                     TimeUnit.MILLISECONDS.sleep(200);
130                 } catch (InterruptedException e) {
131                     logger.error(e.getMessage());
132                 }
133             }
134         }
135     }
136 }

上面是一个通用的延迟过期MAP容器,由两个线程安全的map集合和一个扫描线程组成,该容器会定时移除超时的元素并在移除时触发指定事件expireEvent,该方法的两个参数Key和val分别代表过期元素的键值,定义了元素过期时的触发事件,等待子类实现。

下面是一个使用实例:

 1 import java.util.concurrent.TimeUnit;
 2 
 3 import com.montnets.kafka.Producer;
 4 import com.montnets.smsverify.bean.VerifyAccountSeatBean;
 5 import com.montnets.smsverify.common.StaticValues;
 6 import com.montnets.smsverify.netty.utils.GsonUtil;
 7 
 8 /**
 9  * 进退坐席缓存
10  * 
11  * @ClassName: SeatCache
12  * @Description: 单例
13  * @author: wangs
14  * @date: 2017-12-25 下午2:23:21
15  */
16 public class SeatCache extends BaseExpireMap<String, VerifyAccountSeatBean> {
17     private static SeatCache instance = null;
18     private static Producer producer = null;
19     static long expTime = 0L;
20     static TimeUnit unit = null;
21 
22     private SeatCache(long expTime, TimeUnit unit) {
23         super(expTime, unit);
24     }
25 
26     public synchronized static void init(long expTime, TimeUnit unit) {
27         SeatCache.expTime = expTime;
28         SeatCache.unit = unit;
29         if (instance == null) {
30             instance = new SeatCache(expTime, unit);
31             producer = new Producer();
32         }
33     }
34 
35     public synchronized static SeatCache getInstance() {
36         if (instance == null) {
37             if (unit == null)
38                 throw (new IllegalArgumentException("please call init at first"));
39             instance = new SeatCache(expTime, unit);
40             producer = new Producer();
41         }
42         return instance;
43     }
44 
45     
46     /**
47      * 过期事件
48      */
49     @Override
50     protected void baseExpireEvent(String key, VerifyAccountSeatBean bean) {
51         if(bean!=null)
52             bean.setIsManual(1); //非手动退坐席
53         //更新
54         updateOffOnlie(bean);
55         //写kafka
56         send2kafka(bean);
57     }
58 
59     /**
60      * 退坐席之前更新时间标记
61      * @Title: updateOffOnlie 
62      * @Description: TODO
63      * @param bean
64      * @return: void
65      */
66     public static void updateOffOnlie(VerifyAccountSeatBean bean) {
67         if (bean != null) {
68             long now = System.currentTimeMillis();
69             // 退坐席时间
70             bean.setOutSeatTime(now);
71             // 在线时长
72             bean.setOnlineTime(now - bean.getInSeatTime());
73         }
74     }
75 
76     /**
77      * 退坐席时将数据写入kafka
78      * 
79      * @Title: send2kafka
80      * @Description: TODO
81      * @param topic
82      * @param bean
83      * @return: void
84      */
85     public static void send2kafka(VerifyAccountSeatBean bean) {
86         if (bean == null)
87             return;
88         producer.send(StaticValues.SEAT_DATA, GsonUtil.toJson(bean));
89     }
90 }

推荐一个很强大的过期缓存第三方工具包,com.google.common.cache.Cache ,它提供多种回收策略,如基于创建时间或最后一次访问时间计时回收、基于对象容量大小回收、按照命中率使用LRU算法回收等,并且一样可以自定义过期回收触发事件(写这个工具的时候我还不知道有这么个强大的玩意 - -);另外还提供命中统计的API,功能很全,可以用作数据库到前端页面中间的缓存模块,推荐使用。

 

 世间之事莫强求,凡事太尽,缘分势必早尽。

以上是关于JAVA实现延时过期MAP 支持自定义过期触发事件的主要内容,如果未能解决你的问题,请参考以下文章

Java实现redis缓存效果变量过期

SpringCache自定义过期时间及自动刷新

延时队列常用实现详解

redis 存储一个map 怎么让map中其中一个值设置过期时间,而不是过期掉整个map?

Rabbitmq实现延时消息的两种方式

RabbitMQ实现延时队列(死信队列)