Java本地缓存
撰写时间: 2020-07-06 总共: 6256 字 转载请注明: BY-SA 4.0(除特别声明或转载文章外)
概述
编写一个Java本地缓存类,以及核心方法的单元测试。
Must
实现基本缓存操作 get, set, remove。
读写操作保证线程安全。
支持设置缓存过期时间, TTL或者TTI。
maven 打包编译 (https://maven.apache.org/)。
junit单元测试 (https://junit.org/)。
核心方法变量添加 javadoc。
Better
读写拷贝
持久化
内存回收策略(LRU, FIFO)
Result
实现基本缓存操作 get, set, remove。
读写操作保证线程安全。
支持设置缓存过期时间, TTL或者TTI。
maven 打包编译 (https://maven.apache.org/)。
junit单元测试 (https://junit.org/)。
核心方法变量添加 javadoc。
内存回收策略 FIFO
实现
大家好像都用 ConcurrentHashMap
来当作本地缓存的 store
。
可以查看jdk 1.8 相关文档。
ConcurrentHashMap
支持一组顺序和并行批量操作,与大多数Stream
方法不同,它们被设计为安全并且经常明智地应用,即使是由其他线程同时更新的映射;例如,当计算共享注册表中的值的快照摘要时。 有三种操作,每种具有四种形式,接受键,值,条目和(键,值)参数和/或返回值的函数。 由于
ConcurrentHashMap
的元素不以任何特定的方式排序,并且可能会在不同的并行执行中以不同的顺序进行处理,因此提供的函数的正确性不应取决于任何排序,也不应依赖于可能瞬时变化的任何其他对象或值计算进行中; 除了每一个行动,理想情况下都是无副作用的。
ConcurrentHashMap
采用了分段锁技术,其中Segment
继承于ReentrantLock
。不会像HashTable
那样不管是 put 还是 get 操作都需要做同步处理,理论上ConcurrentHashMap
支持CurrencyLevel
(Segment
数组数量)的线程并发。每当一个线程占用锁访问一个Segment
时,不会影响到其他的Segment
。
基本要求
既然是本地缓存
- 一个用于保存缓存的空间:ConcurrentHashMap。
- 一个用于监控缓存的线程:达到移除失效缓存的目的。
- 一个用于保存缓存内容和缓存相关特定属性的类:比如缓存的键值、时间戳、失效时间、命中次数等。
- 内存回收策略:先进先出(FIFO),最近最久未使用(LRU )等。
- 缓存失效时间设置:TTL,TTI等。
- 单元测试
- 读写克隆?
- 序列化保存做持久化?
- 等等。。
缓存移除策略
即如果缓存满了,从缓存中移除数据的策略;常见的有LFU、LRU、FIFO:
FIFO(First In First Out):先进先出算法,即先放入缓存的先被移除;
LRU(Least Recently Used):最久未使用算法,使用时间距离现在最久的那个被移除;
LFU(Least Frequently Used):最近最少使用算法,一定时间段内使用次数(频率)最少的那个被移除;
过期失效策略
TTL(Time To Live )
存活期,即从缓存中创建时间点开始直到它到期的一个时间段(不管在这个时间段内有没有访问都将过期)
TTI(Time To Idle)
空闲期,即一个数据多久没被访问将从缓存中移除的时间。
https://github.com/XtrueT/LocalCache-java
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* @Author: 霁
* @Date: 2020/7/7 20:32
*/
public class LocalCache implements Serializable {
/***
* 默认缓存过期时间
*/
private static long DEFAULT_TIMEOUT = 3600 * 1000;
/***
* 定时任务执行间隔
* 单位 秒 不小于 1
*/
private static final long TASK_TIME = 10;
/***
* 缓存空间大小
*/
private static int DEFAULT_SIZE = 512;
/***
* 考虑高并发情况下数据安全使用ConcurrentHashMap
*/
private static final ConcurrentHashMap<String, CacheEntity> concurrentHashMap = new ConcurrentHashMap<>(DEFAULT_SIZE);
private static LocalCache localCache = null;
static ConcurrentHashMap<String, CacheEntity> getConcurrentHashMap() {
return concurrentHashMap;
}
static long getDefaultTimeout() {
return DEFAULT_TIMEOUT;
}
static int getDefaultSize() {
return DEFAULT_SIZE;
}
static void setDefaultTimeout(long defaultTimeout) {
DEFAULT_TIMEOUT = defaultTimeout;
}
static void setDefaultSize(int defaultSize) {
DEFAULT_SIZE = defaultSize;
}
private LocalCache() {
}
/***
* 单例模式
* @return LocalCache实例
*/
static LocalCache getInstance() {
if (localCache == null) {
localCache = new LocalCache();
new Thread(new TimeoutTimerThread()).start();
}
return localCache;
}
/***
* 设置缓存
* @param key 键
* @param value 值
* @param expire 过期时间
*/
public synchronized void set(String key, Object value, long expire) throws InterruptedException {
// 已经存在则更新
if (concurrentHashMap.containsKey(key)) {
CacheEntity cacheEntity = concurrentHashMap.get(key);
cacheEntity.setExpire(expire);
cacheEntity.setTimeStamp(System.currentTimeMillis());
cacheEntity.setValue(value);
return;
}
if (localCache.checkIsFull()) {
concurrentHashMap.remove(fifo());
}
TimeUnit.SECONDS.sleep(1);
concurrentHashMap.put(key, new CacheEntity(key, value, expire));
}
/***
* 设置缓存
* @param key 键
* @param value 值
*/
public synchronized void set(String key, Object value) throws InterruptedException {
// 已经存在则更新
if (concurrentHashMap.containsKey(key)) {
CacheEntity cacheEntity = concurrentHashMap.get(key);
cacheEntity.setTimeStamp(System.currentTimeMillis());
cacheEntity.setValue(value);
System.out.println("更新缓存:" + cacheEntity);
return;
}
if (localCache.checkIsFull()) {
concurrentHashMap.remove(fifo());
}
TimeUnit.SECONDS.sleep(1);
concurrentHashMap.put(key, new CacheEntity(key, value));
}
/***
* 获取缓存
* @param key 键
* @return 返回缓存的值
*/
public synchronized Object get(String key) {
CacheEntity cacheEntity = concurrentHashMap.get(key);
if (cacheEntity == null) {
return null;
}
if (localCache.checkIsExpire(cacheEntity)) {
concurrentHashMap.remove(key);
System.out.println("移除失效缓存:" + cacheEntity);
return null;
}
return cacheEntity.getValue();
}
/***
* 移除缓存
* @param key 键
*/
public synchronized void remove(String key) {
concurrentHashMap.remove(key);
}
/***
* 清空缓存
*/
public void clear() {
concurrentHashMap.clear();
}
/***
* 检查是否达到最大缓存空间
* @return true or false
*/
private boolean checkIsFull() {
System.out.println("检查是否已达最大缓存:");
return concurrentHashMap.size() == DEFAULT_SIZE;
}
/***
* 检查是否失效
* @param cacheEntity 缓存对象
* @return true or false
*/
private boolean checkIsExpire(CacheEntity cacheEntity) {
if (cacheEntity.getExpire() == -1) {
return false;
}
return (System.currentTimeMillis() - cacheEntity.getTimeStamp()) >= cacheEntity.getExpire();
}
/***
* 先进先出
* @return 应被移除的键
*/
private String fifo() {
List<CacheEntity> cacheEntities = new ArrayList<>();
for (Object k : ((ConcurrentHashMap) LocalCache.concurrentHashMap).keySet()) {
CacheEntity cacheEntity = (CacheEntity) ((ConcurrentHashMap) LocalCache.concurrentHashMap).get(k);
cacheEntities.add(cacheEntity);
}
cacheEntities.sort((o1, o2) -> Long.compare(o1.getTimeStamp(), o2.getTimeStamp()));
// System.out.println("排序后缓存列表:"+cacheEntities);
System.out.println("已达最大缓存,移除最先进入的缓存:" + cacheEntities.get(0).getKey());
return cacheEntities.get(0).getKey();
}
/***
* 定时器线程类
* 检查缓存过期
*/
private static class TimeoutTimerThread implements Runnable {
@Override
@SuppressWarnings("InfiniteLoopStatement")
public void run() {
while (true) {
try {
if (!concurrentHashMap.isEmpty()) {
TimeUnit.SECONDS.sleep(TASK_TIME);
System.out.println("检查失效缓存:");
System.out.println("当前缓存使用" + concurrentHashMap.size() + "剩余" + (DEFAULT_SIZE - concurrentHashMap.size()));
System.out.println("当前缓存空间数据:" + concurrentHashMap);
for (String key : concurrentHashMap.keySet()) {
CacheEntity cacheEntity = concurrentHashMap.get(key);
if (localCache.checkIsExpire(cacheEntity)) {
concurrentHashMap.remove(key);
System.out.println("移除失效缓存:" + cacheEntity);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/***
* 缓存对象类
*/
private static class CacheEntity implements Serializable {
/***
* 对应的键
*/
private String key;
/***
* 缓存的值
*/
private Object value;
/***
* 创建时间戳
*/
private long timeStamp = System.currentTimeMillis();
/***
* 失效时间
*/
private long expire = DEFAULT_TIMEOUT;
String getKey() {
return key;
}
Object getValue() {
return value;
}
long getTimeStamp() {
return timeStamp;
}
long getExpire() {
return expire;
}
void setValue(Object value) {
this.value = value;
}
void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
void setExpire(long expire) {
this.expire = expire;
}
CacheEntity(String key, Object value, long expire) {
this.key = key;
this.value = value;
this.expire = expire;
}
CacheEntity(String key, Object value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
return "[" + key + "," + value + "," + timeStamp + "," + expire + ']';
}
}
}