package com.common.helper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.lang.Nullable;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
/**
* 本地缓存管理器
* <pre>
* v1.1
* - getByNameSpace
* - isExpired(..) 判断是否已经死亡, 包含物理死亡和逻辑死亡.
*
* v1.0
* - 增加构造, 可指定线程数
*
* v0.0.1
* - 测试期
* </pre>
*
* @author Nisus Liu
* @version 1.1
* @email liuhejun108@163.com
* @date 2018/11/7 17:55
*/
@Slf4j
public abstract class LocalCacheManager<T> {
public static final String NAMESPACE_KEY_JOIN = "::";
/**
* 缓存池子
*/
protected Map<String, T> caches = new ConcurrentHashMap<String, T>();
/**
* 清除缓存的定时延迟任务(刽子手)
*/
protected Map<String, Headsman> headsmans = new ConcurrentHashMap<String, Headsman>();
protected ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(5);
public LocalCacheManager() {
}
/**
* @param poolSize 指定用于清除定时任务的线程数
*/
public LocalCacheManager(int poolSize) {
scheduler.setCorePoolSize(poolSize);
}
/**
* 获取指定key的缓存值
*
* @param key
* @return
*/
public T get(@Nullable String nameSpace, String key) {
if (StringUtil.isEmpty(key)) {
return null;
}
if (!StringUtil.isEmpty(nameSpace)) {
//key实际上是原生key加上命名空间前缀
key = joinNameSpaceAndKey(nameSpace, key);
}
if (isExpired(key)) return null;
return caches.get(key);
}
/**
* 判断是否死亡(实际死亡/逻辑死亡)
* <p>逻辑死亡: 时间上看已经过期, 但是由于秦楚线程阻塞, 还没有来得及清除</p>
*
* @param nskey joinNameSpaceAndKey
* @return
*/
private boolean isExpired(String nskey) {
//防止延迟清除任务阻塞, 导致生存时间误差, 这里实现逻辑清除
Headsman headsman = headsmans.get(nskey);
if (headsman != null) {
//死亡时间 <= 当前时间, 应该设置为逻辑死亡, 返回null
if (headsman.triggerTime <= System.nanoTime()) {
// ?已经逻辑死亡, 但实际没有死亡的值是否要在这里清除呢?
return true;
} else {
return false;
}
} // else: 物理死亡
return !caches.containsKey(nskey);
}
private String joinNameSpaceAndKey(String nameSpace, String key) {
if (StringUtils.isBlank(key)) {
log.debug("`key` is NULL, return");
return null;
}
if (!StringUtil.isEmpty(nameSpace)) {
return nameSpace + NAMESPACE_KEY_JOIN + key;
}
return key;
}
/**
* 增加OR更新缓存
*
* @param key
* @param value
*/
public boolean put(@Nullable String nameSpace, @NotNull String key, T value) {
key = joinNameSpaceAndKey(nameSpace, key);
if (key == null) {
return false;
}
caches.put(key, value);
return true;
}
/**
* 更加OR更新缓存(可设置生存时间TTL,Time To Live)
* <pre>
* Note: 注意避免并发对同一个 key 设置TTL, 结果比较随机, 谁最后执行, 就以谁的TTL设置为准.
* </pre>
*
* @param key
* @param value
* @param ttl
* @param unit
*/
@Deprecated
public void put(@Nullable String nameSpace, String key, T value, long ttl, TimeUnit unit) {
String nsKey = joinNameSpaceAndKey(nameSpace, key);
put(null, nsKey, value);
long triggerTime = System.nanoTime() + unit.toNanos(ttl);
//若此 key 已经有对应的杀死任务, 需要替换掉, 更新生存时间, 以最新的为准
Headsman headsman = headsmans.get(nsKey);
if (headsman == null) { //增加缓存
headsman = new Headsman();
headsmans.put(nsKey, headsman);
headsman.task = new Runnable() {
@Override
public void run() {
//指定时间后清除此 nsKey
T rm = caches.remove(nsKey);
log.trace("cache expired, key: {}, value: {}", nsKey, rm);
}
};
} else { //更新缓存
//对于已经有设置过缓存的 nsKey, 任务用已有的, scheduledFuture 先取消旧的, 在new新的
/*如果任务运行之前调用了该方法,那么任务就不会被运行;
如果任务已经完成或者已经被取消,那么该方法方法不起作用;
如果任务正在运行,并且 cancel 传入参数为 true,那么便会去终止与 Future 关联的任务。*/
headsman.scheduledFuture.cancel(true);
}
headsman.scheduledFuture = new FutureTask(headsman.task, null);
//时间转换成毫秒
headsman.triggerTime = triggerTime;
scheduler.schedule(headsman.scheduledFuture, ttl, unit);
}
public void put(@Nullable String nameSpace, String key, T value, Duration ttl) {
String nsKey = joinNameSpaceAndKey(nameSpace, key);
put(null, nsKey, value);
long triggerTime = System.nanoTime() + ttl.toNanos();
//若此 key 已经有对应的杀死任务, 需要替换掉, 更新生存时间, 以最新的为准
Headsman headsman = headsmans.get(nsKey);
if (headsman == null) { //增加缓存
headsman = new Headsman();
headsmans.put(nsKey, headsman);
headsman.task = new Runnable() {
@Override
public void run() {
//指定时间后清除此 nsKey
T rm = caches.remove(nsKey);
log.trace("cache expired, key: {}, value: {}", nsKey, rm);
}
};
} else { //更新缓存
//对于已经有设置过缓存的 nsKey, 任务用已有的, scheduledFuture 先取消旧的, 在new新的
/*如果任务运行之前调用了该方法,那么任务就不会被运行;
如果任务已经完成或者已经被取消,那么该方法方法不起作用;
如果任务正在运行,并且 cancel 传入参数为 true,那么便会去终止与 Future 关联的任务。*/
headsman.scheduledFuture.cancel(true);
}
headsman.scheduledFuture = new FutureTask(headsman.task, null);
//时间转换成毫秒
headsman.triggerTime = triggerTime;
scheduler.schedule(headsman.scheduledFuture, ttl.toMillis(), TimeUnit.MILLISECONDS);
}
public T evictCache(String nameSpace, String key) {
if (key == null) {
return null;
}
String nsKey = joinNameSpaceAndKey(nameSpace, key);
Headsman hsm = headsmans.remove(nsKey);
if (hsm != null) hsm.scheduledFuture.cancel(true);
return caches.remove(nsKey);
}
/**
* 清空当前命名空间下的所有value
*/
public void evictCache(String nameSpace) {
String prefix = nameSpace + NAMESPACE_KEY_JOIN;
caches.forEach((k, v) -> {
if (k.startsWith(prefix)) {
evictCache(null, k);
}
});
}
public void evictAllCache() {
caches.clear();
}
/**
* 暴露指定命名空间下所有的缓存
*
* @param nameSpace
* @return
*/
public List<T> getByNameSpace(String nameSpace) {
String prefix = nameSpace + NAMESPACE_KEY_JOIN;
List<T> nsVals = new ArrayList<>();
caches.forEach((k, v) -> {
if (k.startsWith(prefix)) {
if (!isExpired(k)) {
nsVals.add(v);
}
}
});
return nsVals;
}
class Headsman {
/**
* 死亡时间, 纳秒值
*/
public long triggerTime;
/**
* 对key更新缓存时, 旧的已有的会取消, 重新设置新的.
*/
public FutureTask scheduledFuture;
/**
* 对于每个 key, task是单例的
*/
public Runnable task;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。