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,功能很全,可以用作數據庫到前端頁面中間的緩存模塊,推薦使用。

 

 世間之事莫強求,凡事太盡,緣分勢必早盡。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM