When learning java concurrency, the book's examples are based caching expanded, so I wanted to write a generic local cache
EDITORIAL
A write cache, the cache needs to consider the underlying storage architecture, cache expires, a cache miss, concurrent read and write and so on, so to write their own local cache will be designed around these points
Cache invalidation
Cache invalidation means that the cache has expired, you need to delete the cache data expired. Delete Delete can be divided into active and passive Erases both
Initiative to remove
- Timing deleted
at the same time setting the value of the expiration time, create a timer so that the timer key expiration time comes, perform the removal of the key immediate
advantages : the memory of the most friendly, guarantee expired key-value pairs as possible is deleted, expired release the memory occupied by the key-value pairs
drawback : forCPU
unfriendly, if the key expired contrast more, delete outdated key-value pairs will occupy a considerable portion ofCPU
the execution time - Deleted regularly
from time to time perform a delete expired key operation, and to reduce the delete operation by limiting the duration and frequency of the deletion operations performedCPU
affect the time of [difficult, long and more difficult to set the frequency of execution]
Passive delete
- Inert delete
only time will remove the keys on key strengths and weaknesses and regularly delete expired inspection opposite
advantages : forCPU
friendship
drawback : memory unfriendly use of inert + Delete to delete a regular basis can be achievedCPU
and the balance of memory, so the local cache + delete cache invalidation inert periodically delete two kinds
Cache eliminated
Cache Cache elimination refers to the number reaches a certain value when you delete a data according to certain rules, regardless of whether the data has expired. Common cache elimination algorithm are:
- FIFO algorithm [
FIFO
]
was first cached data will be the first to be eliminated - The most frequently used algorithm [not
LFU
]
out using a minimum number of data, it is typically achieved by counting for each data, it is calculated once every time, count the number of minimum-out - LRU [
LRU
]
recently used data is not the first to be eliminated, usually achieved through the list, the most recent visit, the newly inserted element moved to the head of the list, out of the list the last element of the local cache selectionLRU
algorithm caching eliminated
Cache structure definition
After choosing a cache miss and cache algorithms can determine the cache out of the structure, the original Dynasty is thread-safe K-V
structure ConcurrentHashMap
plus + doubly linked list structure, but how sweet Recently addicted remember English words, aware of the LinkedHashMap
possible achieve LRU
, lazy use LinkedHashMap
. LinkedHashMap
Based on insertion order storage [default], [recently read can be on the front, most will not often read on the last According storage access order, the order of insertion sequence memory storage instead simply access the accssOrder
set true
can, by default false
. At the same time LinkedHashMap
provides a method for determining whether a least frequently necessary to remove the read data [ removeEldestEntry(Map.Entry<K, CacheNode<K, V>> eldest)
default return false
is not removed] needs to be removed will be able to override this method
Defined cache node
public class CacheNode<K, V> {
/**
* 保存的键
*/
private K key;
/**
* 保存的值
*/
private V value;
/**
* 保存时间
*/
private long gmtCreate;
/**
* 过期时间,单位为毫秒,默认永久有效
*/
private long expireTime = Long.MAX_VALUE;
}
复制代码
Cache structure initialization
/**
* 底层缓存结构
*/
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);
}
复制代码
Description:
- Rewrite the
removeEldestEntry
method, when the cache size exceeds the setmaxCacheSize
will remove elements rarely used - Constructor set
accessOrder
totrue
, sequential access storage eradicate - Size of buffer capacity
(int) Math.ceil(maxCacheSie / DEFAULT_LOAD_FACTOR) + 1
obtained is calculated, so that even reach the setmaxCacheSize
will not trigger operation of the expansion regularExpireStrategy.removeExpireKey(localCache, null);
Start periodically delete task
Periodically delete policy implementation:
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;
}
}
}
}
}
复制代码
Description:
- Use
ScheduledExecutorService
ofscheduleAtFixedRate
realization of regular periodic task - The default one hour once, each execution time is 1 minute, every attempt to delete 100 random elements [If time permits, key expired]
Delete lazy loading strategy to achieve: LazyExpireStrategy.java
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;
}
}
复制代码
Description:
- Determines whether the key exists, the absence of return
null
value - If the key exists, determine whether the expired, but the return period, expired deletion and return
null
value
Implement caching method of operating
- delete
public synchronized V removeKey(K key) { CacheNode<K, V> cacheNode = localCache.remove(key); return cacheNode != null ? cacheNode.getValue() : null; } 复制代码
- Seek
When looking for will go lazy deletion policypublic synchronized V getValue(K key) { return lazyExpireStrategy.removeExpireKey(localCache, key); } 复制代码
- Deposit
deposit value does not fail:
Stored value failure: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; } 复制代码
- Set the cache expiration time
public synchronized void setExpireKey(K key, long expireTime) { if (localCache.get(key) != null) { localCache.get(key).setExpireTime(System.currentTimeMillis() + expireTime); } } 复制代码
- Gets the cache size
public synchronized int getLocalCacheSize() { return localCache.size(); } 复制代码
All methods in order to ensure the security thread are used synchronize
keyword [thread-safe, how sweet will synchronize
not think of any other better way of locking considered read-write locks, but does not work ,,,]
Use gestures
- Creating objects LocalCache
- A posture
The first parameter size of the cache, allowing the storage of the number of cache second parameter periodically delete an object, if it isLocalCache<Integer, Integer> localCache = new LocalCache<>(4, null); 复制代码
null
, use the default execution cycle [regularly delete objects, execution time, the number of executions are the default values] - Pose two
Incoming periodically delete custom objectsRegularExpireStrategy<Integer, Integer> expireStrategy = new RegularExpireStrategy<>(); expireStrategy.setExecuteRate(1); //每隔1分钟执行一次 LocalCache<Integer, Integer> localCache = new LocalCache<>(4, expireStrategy); 复制代码
- A posture
- Cached
for (int i = 0; i < 16; i++) { localCache.putValue(i, i); } 复制代码
- Cached and set the expiration time
localCache.putValue(i, i,1000); 复制代码
- Values are read from the cache
localCache.getValue(i) 复制代码
- Has been set up in the data cache expiration
localCache.setExpireKey(i, 1000) 复制代码
- Gets size of the cache
localCache.getLocalCacheSize() 复制代码
- Delete Cache
localCache.removeKey(i) 复制代码
Write the purpose of learning a local cache, the practical application is recommended to use Google
the Guava Cache
, if you are confident enough of my code, of course, mention WelcomeBug
Optimization points
- Use
ConcurrentHashMap
plus + doubly linked list - Cache invalidation, diversity timer task timing, the cache miss currently using the default unit is ms, the default timer task in minutes, by adding in the process
TimeUnit
parameters selected more time diversity - Concurrent support for the poor, to achieve synchronous [what sweet vegetables too! ! ! ]
ALL
- Upload PW, dependent manner provided for use in other projects, the way to learn how to upload PW
Last Attachment: Project complete code , welcomed the fork , Star
if wrong, please correct me how sweet exchange [really too dishes! ! ! ]