学习java并发的时候,书上的例子是基于缓存展开的,因而就想能够写一个通用的本地缓存
写一个缓存,须要考虑缓存底层存储结构、缓存过时、缓存失效、并发读写等问题,所以本身动手写的本地缓存将围绕这几点进行设计css
缓存失效指的是缓存过时了,须要对过时的缓存数据进行删除。删除能够分为主动删除和被动删除两种java
在设置键值对过时时间的同时,建立一个定时器,让定时器在键过时时间来临时,当即执行对键的删除操做
优势:对内存最友好,保证过时的键值对尽量地被删除,释放过时键值对所占用的内存
缺点:对CPU
不友好,若是过时键值对比较多,删除过时键值对会占用至关一部分CPU
执行时间git
每隔一段时间执行一次删除过时键操做,并经过限制删除操做执行的时长和频率来减小删除操做对CPU
时间的影响【难点,执行时长和频率比较难设置】github
只有在取出键的时候才会对键进行过时检查
优缺点和定时删除相反
优势:对CPU
友好
缺点:对内存不友好
同时使用惰性删除+按期删除,能够取得CPU
和内存的平衡,所以本地缓存的缓存失效采用惰性删除+按期删除两种算法
缓存淘汰指的是缓存的数量达到必定值时按照某种规则删除某个数据,不考虑该数据是否过时。常见的缓存淘汰算法有:缓存
FIFO
】最早存入缓存的数据将最早被淘汰
-最不常用算法【LFU
】
淘汰使用次数最少的数据,通常实现是对每一个数据进行计数,每使用一次就进行计算一次,淘汰计数次数最少的安全
LRU
】最近不使用的数据最早被淘汰,通常实现是经过链表,将最新访问、新插入的元素移到链表头部,淘汰链表最后一个元素
本地缓存将选择LRU
算法实现缓存淘汰并发
选择好了缓存失效和缓存淘汰的算法之后就能够肯定缓存结构了,原先考略的是线程安全的K-V
结构的ConcurrentHashMap
再加+双向链表的结构,但何甜甜最近沉迷记英语单词,同时了解到LinkedHashMap
能够实现LRU
,偷懒使用了LinkedHashMap
。LinkedHashMap
能够基于插入顺序存储【默认】,也能够根据访问顺序存储【最近读取的会放在最前面,最最不常读取的会放在最后】,将插入顺序存储改成访问顺序存储只需将accssOrder
设置为true
便可,默认为false
。同时LinkedHashMap
提供了一个用于判断是否须要移除最不常读取数据的方法【removeEldestEntry(Map.Entry<K, CacheNode<K, V>> eldest)
默认返回false
不移除】,须要移除重写该方法就能够了dom
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 maxCacheSie, ExpireStrategy<K, V> expireStrategy) { //缓存最大容量为初始化的大小 this.maxCacheSie = maxCacheSie; //缓存最大容量 => 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); }
说明:ide
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; } }
说明:
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对象
姿式一
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
ConcurrentHashMap
再加+双向链表TimeUnit
参数时间选择更多样性最后附:项目完整代码,欢迎fork,star 若有错误,欢迎指正交流【何甜甜真的太菜了!!!】