学习java并发的时候,书上的例子是基于缓存展开的,因而就想能够写一个通用的本地缓存css
写一个缓存,须要考虑缓存底层存储结构、缓存过时、缓存失效、并发读写等问题,所以本身动手写的本地缓存将围绕这几点进行设计java
缓存失效指的是缓存过时了,须要对过时的缓存数据进行删除。删除能够分为主动删除和被动删除两种git
CPU
不友好,若是过时键值对比较多,删除过时键值对会占用至关一部分CPU
执行时间CPU
时间的影响【难点,执行时长和频率比较难设置】CPU
友好CPU
和内存的平衡,所以本地缓存的缓存失效采用惰性删除+按期删除两种缓存淘汰指的是缓存的数量达到必定值时按照某种规则删除某个数据,不考虑该数据是否过时。常见的缓存淘汰算法有:github
FIFO
】LFU
】LRU
】 LRU
算法实现缓存淘汰选择好了缓存失效和缓存淘汰的算法之后就能够肯定缓存结构了,原先考略的是线程安全的K-V
结构的ConcurrentHashMap
再加+双向链表的结构,但何甜甜最近沉迷记英语单词,同时了解到LinkedHashMap
能够实现LRU
,偷懒使用了LinkedHashMap
。LinkedHashMap
能够基于插入顺序存储【默认】,也能够根据访问顺序存储【最近读取的会放在最前面,最最不常读取的会放在最后】,将插入顺序存储改成访问顺序存储只需将accssOrder
设置为true
便可,默认为false
。同时LinkedHashMap
提供了一个用于判断是否须要移除最不常读取数据的方法【removeEldestEntry(Map.Entry<K, CacheNode<K, V>> eldest)
默认返回false
不移除】,须要移除重写该方法就能够了算法
public class CacheNode<K, V> {
/**
* 保存的键
*/
private K key;
/**
* 保存的值
*/
private V value;
/**
* 保存时间
*/
private long gmtCreate;
/**
* 过时时间,单位为毫秒,默认永久有效
*/
private long expireTime = Long.MAX_VALUE;
}
复制代码
/**
* 底层缓存结构
*/
private LinkedHashMap<K, CacheNode<K, V>> localCache;
/**
* 负载因子
*/
private final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 缓存过时清理策略
*/
private ExpireStrategy<K, V> lazyExpireStrategy = new LazyExpireStrategy<>();
private ExpireStrategy<K, V> regularExpireStrategy;
private int maxCacheSie;
/**
* 构造函数
*
* @param expireStrategy 缓存失效策略实现类,针对的是按期失效缓存,传入null,按期失效缓存类为默认配置值
* @param maxCacheSie 缓存最大容许存放的数量,缓存失效策略根据这个值触发
*/
public LocalCache(int maxCacheSize, ExpireStrategy<K, V> expireStrategy) {
//缓存最大容量为初始化的大小
this.maxCacheSize = maxCacheSize;
//缓存最大容量 => initialCapacity * DEFAULT_LOAD_FACTOR,避免扩容操做
int initialCapacity = (int) Math.ceil(maxCacheSie / DEFAULT_LOAD_FACTOR) + 1;
//accessOrder设置为true,根据访问顺序而不是插入顺序
this.localCache = new LinkedHashMap<K, CacheNode<K, V>>(initialCapacity, DEFAULT_LOAD_FACTOR, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, CacheNode<K, V>> eldest) {
return size() > maxCacheSie;
}
};
this.regularExpireStrategy = (expireStrategy == null ? new RegularExpireStrategy<>() : expireStrategy);
//启动定时清除过时键任务
regularExpireStrategy.removeExpireKey(localCache, null);
}
复制代码
说明:缓存
removeEldestEntry
方法,当缓存大小超过了设置的maxCacheSize
才会移除不常使用的元素accessOrder
为true
,根绝访问顺序存储(int) Math.ceil(maxCacheSie / DEFAULT_LOAD_FACTOR) + 1
计算获得,这样即便达到设置的maxCacheSize
也不会触发扩容操做regularExpireStrategy.removeExpireKey(localCache, null);
启动按期删除任务public class RegularExpireStrategy<K, V> implements ExpireStrategy<K, V> {
Logger logger = LoggerFactory.getLogger(getClass());
/**
* 按期任务每次执行删除操做的次数
*/
private long executeCount = 100;
/**
* 按期任务执行时常 【1分钟】
*/
private long executeDuration = 1000 * 60;
/**
* 按期任务执行的频率
*/
private long executeRate = 60;
//get and set
public long getExecuteCount() {
return executeCount;
}
public void setExecuteCount(long executeCount) {
this.executeCount = executeCount;
}
public long getExecuteDuration() {
return executeDuration;
}
public void setExecuteDuration(long executeDuration) {
this.executeDuration = executeDuration;
}
public long getExecuteRate() {
return executeRate;
}
public void setExecuteRate(long executeRate) {
this.executeRate = executeRate;
}
/**
* 清空过时Key-Value
*
* @param localCache 本地缓存底层使用的存储结构
* @param key 缓存的键
* @return 过时的值
*/
@Override
public V removeExpireKey(LinkedHashMap<K, CacheNode<K, V>> localCache, K key) {
logger.info("开启按期清除过时key任务");
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
//定时周期任务,executeRate分钟以后执行,默认1小时执行一次
executor.scheduleAtFixedRate(new MyTask(localCache), 0, executeRate, TimeUnit.MINUTES);
return null;
}
/**
* 自定义任务
*/
private class MyTask<K, V> implements Runnable {
private LinkedHashMap<K, CacheNode<K, V>> localCache;
public MyTask(LinkedHashMap<K, CacheNode<K, V>> localCache) {
this.localCache = localCache;
}
@Override
public void run() {
long start = System.currentTimeMillis();
List<K> keyList = localCache.keySet().stream().collect(Collectors.toList());
int size = keyList.size();
Random random = new Random();
for (int i = 0; i < executeCount; i++) {
K randomKey = keyList.get(random.nextInt(size));
if (localCache.get(randomKey).getExpireTime() - System.currentTimeMillis() < 0) {
logger.info("key:{}已过时,进行按期删除key操做", randomKey);
localCache.remove(randomKey);
}
//超时执行退出
if (System.currentTimeMillis() - start > executeDuration) {
break;
}
}
}
}
}
复制代码
说明:安全
ScheduledExecutorService
的scheduleAtFixedRate
实现定时周期任务public class LazyExpireStrategy<K, V> implements ExpireStrategy<K, V> {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 清空过时Key-Value
*
* @param localCache 本地缓存底层使用的存储结构
* @param key 缓存的键
* @return 过时的值
*/
@Override
public V removeExpireKey(LinkedHashMap<K, CacheNode<K, V>> localCache, K key) {
CacheNode<K, V> baseCacheValue = localCache.get(key);
//值不存在
if (baseCacheValue == null) {
logger.info("key:{}对应的value不存在", key);
return null;
} else {
//值存在而且未过时
if (baseCacheValue.getExpireTime() - System.currentTimeMillis() > 0) {
return baseCacheValue.getValue();
}
}
logger.info("key:{}已过时,进行懒删除key操做", key);
localCache.remove(key);
return null;
}
}
复制代码
说明:bash
null
值null
值public synchronized V removeKey(K key) {
CacheNode<K, V> cacheNode = localCache.remove(key);
return cacheNode != null ? cacheNode.getValue() : null;
}
复制代码
public synchronized V getValue(K key) {
return lazyExpireStrategy.removeExpireKey(localCache, key);
}
复制代码
查找的时候会走懒删除策略public synchronized V putValue(K key, V value) {
CacheNode<K, V> cacheNode = new CacheNode<>();
cacheNode.setKey(key);
cacheNode.setValue(value);
localCache.put(key, cacheNode);
// 返回添加的值
return value;
}
复制代码
存入的值失效:public synchronized V putValue(K key, V value, long expireTime) {
CacheNode<K, V> cacheNode = new CacheNode<>();
cacheNode.setKey(key);
cacheNode.setValue(value);
cacheNode.setGmtCreate(System.currentTimeMillis() + expireTime);
localCache.put(key, cacheNode);
// 返回添加的值
return value;
}
复制代码
public synchronized void setExpireKey(K key, long expireTime) {
if (localCache.get(key) != null) {
localCache.get(key).setExpireTime(System.currentTimeMillis() + expireTime);
}
}
复制代码
public synchronized int getLocalCacheSize() {
return localCache.size();
}
复制代码
全部方法为了保证线程安全都使用了synchronize
关键字【线程安全,何甜甜只会synchronize
,没有想到其余更好的加锁方式、考虑了读写锁可是行不通、、、】并发
LocalCache<Integer, Integer> localCache = new LocalCache<>(4, null);
复制代码
第一个参数缓存的大小,容许存放缓存的数量 第二个参数按期删除对象,若是为null
,使用默认的按期删除对象【执行周期、执行时间、执行次数都为默认值】RegularExpireStrategy<Integer, Integer> expireStrategy = new RegularExpireStrategy<>();
expireStrategy.setExecuteRate(1); //每隔1分钟执行一次
LocalCache<Integer, Integer> localCache = new LocalCache<>(4, expireStrategy);
复制代码
传入自定义的按期删除对象for (int i = 0; i < 16; i++) {
localCache.putValue(i, i);
}
复制代码
localCache.putValue(i, i,1000);
复制代码
localCache.getValue(i)
复制代码
localCache.setExpireKey(i, 1000)
复制代码
localCache.getLocalCacheSize()
复制代码
localCache.removeKey(i)
复制代码
基于学习的目的写了一个本地缓存,实际应用中仍是推荐使用Google
的Guava Cache
,若是你对个人代码足够自信,固然也欢迎使用提Bug
dom
ConcurrentHashMap
再加+双向链表TimeUnit
参数时间选择更多样性最后附:项目完整代码,欢迎fork,star 若有错误,欢迎指正交流【何甜甜真的太菜了!!!】