JAVA實現延時過期MAP 支持自定義過期觸發事件
阿新 • • 發佈:2018-03-25
keys 算法 public 寫入 hash pty static 實現 ssa
如題,直接上代碼:
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: TODO13 * @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 privateTimeUnit 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: TODO35 * @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 支持自定義過期觸發事件