Commit 45cda665 authored by ma yanling's avatar ma yanling
Browse files

project commit

parent ad2fb30a
Pipeline #2354 failed with stages
in 0 seconds
package cn.hutool.cache;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 全局缓存清理定时器池,用于在需要过期支持的缓存对象中超时任务池
*
* @author looly
*/
public enum GlobalPruneTimer {
/**
* 单例对象
*/
INSTANCE;
/**
* 缓存任务计数
*/
private final AtomicInteger cacheTaskNumber = new AtomicInteger(1);
/**
* 定时器
*/
private ScheduledExecutorService pruneTimer;
/**
* 构造
*/
GlobalPruneTimer() {
create();
}
/**
* 启动定时任务
*
* @param task 任务
* @param delay 周期
* @return {@link ScheduledFuture}对象,可手动取消此任务
*/
public ScheduledFuture<?> schedule(Runnable task, long delay) {
return this.pruneTimer.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS);
}
/**
* 创建定时器
*/
public void create() {
if (null != pruneTimer) {
shutdownNow();
}
this.pruneTimer = new ScheduledThreadPoolExecutor(1, r -> ThreadUtil.newThread(r, StrUtil.format("Pure-Timer-{}", cacheTaskNumber.getAndIncrement())));
}
/**
* 销毁全局定时器
*/
public void shutdown() {
if (null != pruneTimer) {
pruneTimer.shutdown();
}
}
/**
* 销毁全局定时器
*
* @return 销毁时未被执行的任务列表
*/
public List<Runnable> shutdownNow() {
if (null != pruneTimer) {
return pruneTimer.shutdownNow();
}
return null;
}
}
package cn.hutool.cache.file;
import java.io.File;
import java.io.Serializable;
import cn.hutool.cache.Cache;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
/**
* 文件缓存,以解决频繁读取文件引起的性能问题
* @author Looly
*
*/
public abstract class AbstractFileCache implements Serializable{
private static final long serialVersionUID = 1L;
/** 容量 */
protected final int capacity;
/** 缓存的最大文件大小,文件大于此大小时将不被缓存 */
protected final int maxFileSize;
/** 默认超时时间,0表示无默认超时 */
protected final long timeout;
/** 缓存实现 */
protected final Cache<File, byte[]> cache;
/** 已使用缓存空间 */
protected int usedSize;
/**
* 构造
* @param capacity 缓存容量
* @param maxFileSize 文件最大大小
* @param timeout 默认超时时间,0表示无默认超时
*/
public AbstractFileCache(int capacity, int maxFileSize, long timeout) {
this.capacity = capacity;
this.maxFileSize = maxFileSize;
this.timeout = timeout;
this.cache = initCache();
}
/**
* @return 缓存容量(byte数)
*/
public int capacity() {
return capacity;
}
/**
* @return 已使用空间大小(byte数)
*/
public int getUsedSize() {
return usedSize;
}
/**
* @return 允许被缓存文件的最大byte数
*/
public int maxFileSize() {
return maxFileSize;
}
/**
* @return 缓存的文件数
*/
public int getCachedFilesCount() {
return cache.size();
}
/**
* @return 超时时间
*/
public long timeout() {
return this.timeout;
}
/**
* 清空缓存
*/
public void clear() {
cache.clear();
usedSize = 0;
}
// ---------------------------------------------------------------- get
/**
* 获得缓存过的文件bytes
* @param path 文件路径
* @return 缓存过的文件bytes
* @throws IORuntimeException IO异常
*/
public byte[] getFileBytes(String path) throws IORuntimeException {
return getFileBytes(new File(path));
}
/**
* 获得缓存过的文件bytes
* @param file 文件
* @return 缓存过的文件bytes
* @throws IORuntimeException IO异常
*/
public byte[] getFileBytes(File file) throws IORuntimeException {
byte[] bytes = cache.get(file);
if (bytes != null) {
return bytes;
}
// add file
bytes = FileUtil.readBytes(file);
if ((maxFileSize != 0) && (file.length() > maxFileSize)) {
//大于缓存空间,不缓存,直接返回
return bytes;
}
usedSize += bytes.length;
//文件放入缓存,如果usedSize > capacity,purge()方法将被调用
cache.put(file, bytes);
return bytes;
}
// ---------------------------------------------------------------- protected method start
/**
* 初始化实现文件缓存的缓存对象
* @return {@link Cache}
*/
protected abstract Cache<File, byte[]> initCache();
// ---------------------------------------------------------------- protected method end
}
package cn.hutool.cache.file;
import java.io.File;
import cn.hutool.cache.Cache;
import cn.hutool.cache.impl.LFUCache;
/**
* 使用LFU缓存文件,以解决频繁读取文件引起的性能问题
* @author Looly
*
*/
public class LFUFileCache extends AbstractFileCache{
private static final long serialVersionUID = 1L;
/**
* 构造<br>
* 最大文件大小为缓存容量的一半<br>
* 默认无超时
* @param capacity 缓存容量
*/
public LFUFileCache(int capacity) {
this(capacity, capacity / 2, 0);
}
/**
* 构造<br>
* 默认无超时
* @param capacity 缓存容量
* @param maxFileSize 最大文件大小
*/
public LFUFileCache(int capacity, int maxFileSize) {
this(capacity, maxFileSize, 0);
}
/**
* 构造
* @param capacity 缓存容量
* @param maxFileSize 文件最大大小
* @param timeout 默认超时时间,0表示无默认超时
*/
public LFUFileCache(int capacity, int maxFileSize, long timeout) {
super(capacity, maxFileSize, timeout);
}
@Override
protected Cache<File, byte[]> initCache() {
return new LFUCache<File, byte[]>(LFUFileCache.this.capacity, LFUFileCache.this.timeout) {
private static final long serialVersionUID = 1L;
@Override
public boolean isFull() {
return LFUFileCache.this.usedSize > this.capacity;
}
@Override
protected void onRemove(File key, byte[] cachedObject) {
usedSize -= cachedObject.length;
}
};
}
}
package cn.hutool.cache.file;
import java.io.File;
import cn.hutool.cache.Cache;
import cn.hutool.cache.impl.LRUCache;
/**
* 使用LRU缓存文件,以解决频繁读取文件引起的性能问题
* @author Looly
*
*/
public class LRUFileCache extends AbstractFileCache{
private static final long serialVersionUID = 1L;
/**
* 构造<br>
* 最大文件大小为缓存容量的一半<br>
* 默认无超时
* @param capacity 缓存容量
*/
public LRUFileCache(int capacity) {
this(capacity, capacity / 2, 0);
}
/**
* 构造<br>
* 默认无超时
* @param capacity 缓存容量
* @param maxFileSize 最大文件大小
*/
public LRUFileCache(int capacity, int maxFileSize) {
this(capacity, maxFileSize, 0);
}
/**
* 构造
* @param capacity 缓存容量
* @param maxFileSize 文件最大大小
* @param timeout 默认超时时间,0表示无默认超时
*/
public LRUFileCache(int capacity, int maxFileSize, long timeout) {
super(capacity, maxFileSize, timeout);
}
@Override
protected Cache<File, byte[]> initCache() {
return new LRUCache<File, byte[]>(LRUFileCache.this.capacity, super.timeout) {
private static final long serialVersionUID = 1L;
@Override
public boolean isFull() {
return LRUFileCache.this.usedSize > this.capacity;
}
@Override
protected void onRemove(File key, byte[] cachedObject) {
usedSize -= cachedObject.length;
}
};
}
}
/**
* 提供针对文件的缓存实现
*
* @author looly
*
*/
package cn.hutool.cache.file;
\ No newline at end of file
package cn.hutool.cache.impl;
import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheListener;
import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.lang.mutable.Mutable;
import cn.hutool.core.lang.mutable.MutableObj;
import cn.hutool.core.map.SafeConcurrentHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/**
* 超时和限制大小的缓存的默认实现<br>
* 继承此抽象缓存需要:<br>
* <ul>
* <li>创建一个新的Map</li>
* <li>实现 {@code prune} 策略</li>
* </ul>
*
* @param <K> 键类型
* @param <V> 值类型
* @author Looly, jodd
*/
public abstract class AbstractCache<K, V> implements Cache<K, V> {
private static final long serialVersionUID = 1L;
protected Map<Mutable<K>, CacheObj<K, V>> cacheMap;
/**
* 写的时候每个key一把锁,降低锁的粒度
*/
protected final SafeConcurrentHashMap<K, Lock> keyLockMap = new SafeConcurrentHashMap<>();
/**
* 返回缓存容量,{@code 0}表示无大小限制
*/
protected int capacity;
/**
* 缓存失效时长, {@code 0} 表示无限制,单位毫秒
*/
protected long timeout;
/**
* 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。
*/
protected boolean existCustomTimeout;
/**
* 命中数,即命中缓存计数
*/
protected LongAdder hitCount = new LongAdder();
/**
* 丢失数,即未命中缓存计数
*/
protected LongAdder missCount = new LongAdder();
/**
* 缓存监听
*/
protected CacheListener<K, V> listener;
// ---------------------------------------------------------------- put start
@Override
public void put(K key, V object) {
put(key, object, timeout);
}
/**
* 加入元素,无锁
*
* @param key 键
* @param object 值
* @param timeout 超时时长
* @since 4.5.16
*/
protected void putWithoutLock(K key, V object, long timeout) {
CacheObj<K, V> co = new CacheObj<>(key, object, timeout);
if (timeout != 0) {
existCustomTimeout = true;
}
if (isFull()) {
pruneCache();
}
cacheMap.put(MutableObj.of(key), co);
}
// ---------------------------------------------------------------- put end
// ---------------------------------------------------------------- get start
/**
* @return 命中数
*/
public long getHitCount() {
return hitCount.sum();
}
/**
* @return 丢失数
*/
public long getMissCount() {
return missCount.sum();
}
@Override
public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {
V v = get(key, isUpdateLastAccess);
if (null == v && null != supplier) {
//每个key单独获取一把锁,降低锁的粒度提高并发能力,see pr#1385@Github
final Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock());
keyLock.lock();
try {
// 双重检查锁,防止在竞争锁的过程中已经有其它线程写入
final CacheObj<K, V> co = getWithoutLock(key);
if (null == co || co.isExpired()) {
try {
v = supplier.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
put(key, v, this.timeout);
} else {
v = co.get(isUpdateLastAccess);
}
} finally {
keyLock.unlock();
keyLockMap.remove(key);
}
}
return v;
}
/**
* 获取键对应的{@link CacheObj}
* @param key 键,实际使用时会被包装为{@link MutableObj}
* @return {@link CacheObj}
* @since 5.8.0
*/
protected CacheObj<K, V> getWithoutLock(K key){
return this.cacheMap.get(MutableObj.of(key));
}
// ---------------------------------------------------------------- get end
@Override
public Iterator<V> iterator() {
CacheObjIterator<K, V> copiedIterator = (CacheObjIterator<K, V>) this.cacheObjIterator();
return new CacheValuesIterator<>(copiedIterator);
}
// ---------------------------------------------------------------- prune start
/**
* 清理实现<br>
* 子类实现此方法时无需加锁
*
* @return 清理数
*/
protected abstract int pruneCache();
// ---------------------------------------------------------------- prune end
// ---------------------------------------------------------------- common start
@Override
public int capacity() {
return capacity;
}
/**
* @return 默认缓存失效时长。<br>
* 每个对象可以单独设置失效时长
*/
@Override
public long timeout() {
return timeout;
}
/**
* 只有设置公共缓存失效时长或每个对象单独的失效时长时清理可用
*
* @return 过期对象清理是否可用,内部使用
*/
protected boolean isPruneExpiredActive() {
return (timeout != 0) || existCustomTimeout;
}
@Override
public boolean isFull() {
return (capacity > 0) && (cacheMap.size() >= capacity);
}
@Override
public int size() {
return cacheMap.size();
}
@Override
public boolean isEmpty() {
return cacheMap.isEmpty();
}
@Override
public String toString() {
return this.cacheMap.toString();
}
// ---------------------------------------------------------------- common end
/**
* 设置监听
*
* @param listener 监听
* @return this
* @since 5.5.2
*/
@Override
public AbstractCache<K, V> setListener(CacheListener<K, V> listener) {
this.listener = listener;
return this;
}
/**
* 返回所有键
*
* @return 所有键
* @since 5.5.9
*/
public Set<K> keySet(){
return this.cacheMap.keySet().stream().map(Mutable::get).collect(Collectors.toSet());
}
/**
* 对象移除回调。默认无动作<br>
* 子类可重写此方法用于监听移除事件,如果重写,listener将无效
*
* @param key 键
* @param cachedObject 被缓存的对象
*/
protected void onRemove(K key, V cachedObject) {
final CacheListener<K, V> listener = this.listener;
if (null != listener) {
listener.onRemove(key, cachedObject);
}
}
/**
* 移除key对应的对象,不加锁
*
* @param key 键
* @param withMissCount 是否计数丢失数
* @return 移除的对象,无返回null
*/
protected CacheObj<K, V> removeWithoutLock(K key, boolean withMissCount) {
final CacheObj<K, V> co = cacheMap.remove(MutableObj.of(key));
if (withMissCount) {
// 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1
this.missCount.increment();
}
return co;
}
/**
* 获取所有{@link CacheObj}值的{@link Iterator}形式
* @return {@link Iterator}
* @since 5.8.0
*/
protected Iterator<CacheObj<K, V>> cacheObjIter(){
return this.cacheMap.values().iterator();
}
}
package cn.hutool.cache.impl;
import cn.hutool.core.date.DateUtil;
import java.io.Serializable;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;
/**
* 缓存对象
*
* @param <K> Key类型
* @param <V> Value类型
* @author Looly
*/
public class CacheObj<K, V> implements Serializable {
private static final long serialVersionUID = 1L;
protected final K key;
protected final V obj;
/**
* 上次访问时间
*/
protected volatile long lastAccess;
/**
* 访问次数
*/
protected AtomicLong accessCount = new AtomicLong();
/**
* 对象存活时长,0表示永久存活
*/
protected final long ttl;
/**
* 构造
*
* @param key 键
* @param obj 值
* @param ttl 超时时长
*/
protected CacheObj(K key, V obj, long ttl) {
this.key = key;
this.obj = obj;
this.ttl = ttl;
this.lastAccess = System.currentTimeMillis();
}
/**
* 获取键
*
* @return 键
* @since 4.0.10
*/
public K getKey() {
return this.key;
}
/**
* 获取值
*
* @return 值
* @since 4.0.10
*/
public V getValue() {
return this.obj;
}
/**
* 获取对象存活时长,即超时总时长,0表示无限
*
* @return 对象存活时长
* @since 5.7.17
*/
public long getTtl() {
return this.ttl;
}
/**
* 获取过期时间,返回{@code null}表示永不过期
*
* @return 此对象的过期时间,返回{@code null}表示永不过期
* @since 5.7.17
*/
public Date getExpiredTime(){
if(this.ttl > 0){
return DateUtil.date(this.lastAccess + this.ttl);
}
return null;
}
/**
* 获取上次访问时间
*
* @return 上次访问时间
* @since 5.7.17
*/
public long getLastAccess() {
return this.lastAccess;
}
@Override
public String toString() {
return "CacheObj [key=" + key + ", obj=" + obj + ", lastAccess=" + lastAccess + ", accessCount=" + accessCount + ", ttl=" + ttl + "]";
}
/**
* 判断是否过期
*
* @return 是否过期
*/
protected boolean isExpired() {
if (this.ttl > 0) {
// 此处不考虑时间回拨
return (System.currentTimeMillis() - this.lastAccess) > this.ttl;
}
return false;
}
/**
* 获取值
*
* @param isUpdateLastAccess 是否更新最后访问时间
* @return 获得对象
* @since 4.0.10
*/
protected V get(boolean isUpdateLastAccess) {
if (isUpdateLastAccess) {
lastAccess = System.currentTimeMillis();
}
accessCount.getAndIncrement();
return this.obj;
}
}
package cn.hutool.cache.impl;
import java.io.Serializable;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* {@link cn.hutool.cache.impl.AbstractCache} 的CacheObj迭代器.
*
* @author looly
*
* @param <K> 键类型
* @param <V> 值类型
* @since 4.0.10
*/
public class CacheObjIterator<K, V> implements Iterator<CacheObj<K, V>>, Serializable {
private static final long serialVersionUID = 1L;
private final Iterator<CacheObj<K, V>> iterator;
private CacheObj<K, V> nextValue;
/**
* 构造
*
* @param iterator 原{@link Iterator}
*/
CacheObjIterator(Iterator<CacheObj<K, V>> iterator) {
this.iterator = iterator;
nextValue();
}
/**
* @return 是否有下一个值
*/
@Override
public boolean hasNext() {
return nextValue != null;
}
/**
* @return 下一个值
*/
@Override
public CacheObj<K, V> next() {
if (false == hasNext()) {
throw new NoSuchElementException();
}
final CacheObj<K, V> cachedObject = nextValue;
nextValue();
return cachedObject;
}
/**
* 从缓存中移除没有过期的当前值,此方法不支持
*/
@Override
public void remove() {
throw new UnsupportedOperationException("Cache values Iterator is not support to modify.");
}
/**
* 下一个值,当不存在则下一个值为null
*/
private void nextValue() {
while (iterator.hasNext()) {
nextValue = iterator.next();
if (nextValue.isExpired() == false) {
return;
}
}
nextValue = null;
}
}
package cn.hutool.cache.impl;
import java.io.Serializable;
import java.util.Iterator;
/**
* {@link cn.hutool.cache.impl.AbstractCache} 的值迭代器.
* @author looly
*
* @param <V> 迭代对象类型
*/
public class CacheValuesIterator<V> implements Iterator<V>, Serializable {
private static final long serialVersionUID = 1L;
private final CacheObjIterator<?, V> cacheObjIter;
/**
* 构造
* @param iterator 原{@link CacheObjIterator}
*/
CacheValuesIterator(CacheObjIterator<?, V> iterator) {
this.cacheObjIter = iterator;
}
/**
* @return 是否有下一个值
*/
@Override
public boolean hasNext() {
return this.cacheObjIter.hasNext();
}
/**
* @return 下一个值
*/
@Override
public V next() {
return cacheObjIter.next().getValue();
}
/**
* 从缓存中移除没有过期的当前值,不支持此方法
*/
@Override
public void remove() {
cacheObjIter.remove();
}
}
package cn.hutool.cache.impl;
import java.util.Iterator;
import java.util.LinkedHashMap;
/**
* FIFO(first in first out) 先进先出缓存.
*
* <p>
* 元素不停的加入缓存直到缓存满为止,当缓存满时,清理过期缓存对象,清理后依旧满则删除先入的缓存(链表首部对象)<br>
* 优点:简单快速 <br>
* 缺点:不灵活,不能保证最常用的对象总是被保留
* </p>
*
* @param <K> 键类型
* @param <V> 值类型
* @author Looly
*/
public class FIFOCache<K, V> extends StampedCache<K, V> {
private static final long serialVersionUID = 1L;
/**
* 构造,默认对象不过期
*
* @param capacity 容量
*/
public FIFOCache(int capacity) {
this(capacity, 0);
}
/**
* 构造
*
* @param capacity 容量
* @param timeout 过期时长
*/
public FIFOCache(int capacity, long timeout) {
this.capacity = capacity;
this.timeout = timeout;
cacheMap = new LinkedHashMap<>(capacity + 1, 1.0f, false);
}
/**
* 先进先出的清理策略<br>
* 先遍历缓存清理过期的缓存对象,如果清理后还是满的,则删除第一个缓存对象
*/
@Override
protected int pruneCache() {
int count = 0;
CacheObj<K, V> first = null;
// 清理过期对象并找出链表头部元素(先入元素)
final Iterator<CacheObj<K, V>> values = cacheObjIter();
if (isPruneExpiredActive()) {
// 清理过期对象并找出链表头部元素(先入元素)
while (values.hasNext()) {
CacheObj<K, V> co = values.next();
if (co.isExpired()) {
values.remove();
onRemove(co.key, co.obj);
count++;
continue;
}
if (first == null) {
first = co;
}
}
} else {
first = values.hasNext() ? values.next() : null;
}
// 清理结束后依旧是满的,则删除第一个被缓存的对象
if (isFull() && null != first) {
removeWithoutLock(first.key, false);
onRemove(first.key, first.obj);
count++;
}
return count;
}
}
package cn.hutool.cache.impl;
import java.util.HashMap;
import java.util.Iterator;
/**
* LFU(least frequently used) 最少使用率缓存<br>
* 根据使用次数来判定对象是否被持续缓存<br>
* 使用率是通过访问次数计算的。<br>
* 当缓存满时清理过期对象。<br>
* 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。
*
* @author Looly,jodd
*
* @param <K> 键类型
* @param <V> 值类型
*/
public class LFUCache<K, V> extends StampedCache<K, V> {
private static final long serialVersionUID = 1L;
/**
* 构造
*
* @param capacity 容量
*/
public LFUCache(int capacity) {
this(capacity, 0);
}
/**
* 构造
*
* @param capacity 容量
* @param timeout 过期时长
*/
public LFUCache(int capacity, long timeout) {
if(Integer.MAX_VALUE == capacity) {
capacity -= 1;
}
this.capacity = capacity;
this.timeout = timeout;
cacheMap = new HashMap<>(capacity + 1, 1.0f);
}
// ---------------------------------------------------------------- prune
/**
* 清理过期对象。<br>
* 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。
*
* @return 清理个数
*/
@Override
protected int pruneCache() {
int count = 0;
CacheObj<K, V> comin = null;
// 清理过期对象并找出访问最少的对象
Iterator<CacheObj<K, V>> values = cacheObjIter();
CacheObj<K, V> co;
while (values.hasNext()) {
co = values.next();
if (co.isExpired() == true) {
values.remove();
onRemove(co.key, co.obj);
count++;
continue;
}
//找出访问最少的对象
if (comin == null || co.accessCount.get() < comin.accessCount.get()) {
comin = co;
}
}
// 减少所有对象访问量,并清除减少后为0的访问对象
if (isFull() && comin != null) {
long minAccessCount = comin.accessCount.get();
values = cacheObjIter();
CacheObj<K, V> co1;
while (values.hasNext()) {
co1 = values.next();
if (co1.accessCount.addAndGet(-minAccessCount) <= 0) {
values.remove();
onRemove(co1.key, co1.obj);
count++;
}
}
}
return count;
}
}
package cn.hutool.cache.impl;
import cn.hutool.core.lang.mutable.Mutable;
import cn.hutool.core.map.FixedLinkedHashMap;
import java.util.Iterator;
/**
* LRU (least recently used)最近最久未使用缓存<br>
* 根据使用时间来判定对象是否被持续缓存<br>
* 当对象被访问时放入缓存,当缓存满了,最久未被使用的对象将被移除。<br>
* 此缓存基于LinkedHashMap,因此当被缓存的对象每被访问一次,这个对象的key就到链表头部。<br>
* 这个算法简单并且非常快,他比FIFO有一个显著优势是经常使用的对象不太可能被移除缓存。<br>
* 缺点是当缓存满时,不能被很快的访问。
* @author Looly,jodd
*
* @param <K> 键类型
* @param <V> 值类型
*/
public class LRUCache<K, V> extends ReentrantCache<K, V> {
private static final long serialVersionUID = 1L;
/**
* 构造<br>
* 默认无超时
* @param capacity 容量
*/
public LRUCache(int capacity) {
this(capacity, 0);
}
/**
* 构造
* @param capacity 容量
* @param timeout 默认超时时间,单位:毫秒
*/
public LRUCache(int capacity, long timeout) {
if(Integer.MAX_VALUE == capacity) {
capacity -= 1;
}
this.capacity = capacity;
this.timeout = timeout;
//链表key按照访问顺序排序,调用get方法后,会将这次访问的元素移至头部
final FixedLinkedHashMap<Mutable<K>, CacheObj<K, V>> fixedLinkedHashMap = new FixedLinkedHashMap<>(capacity);
fixedLinkedHashMap.setRemoveListener(entry -> {
if(null != listener){
listener.onRemove(entry.getKey().get(), entry.getValue().getValue());
}
});
cacheMap = fixedLinkedHashMap;
}
// ---------------------------------------------------------------- prune
/**
* 只清理超时对象,LRU的实现会交给{@code LinkedHashMap}
*/
@Override
protected int pruneCache() {
if (isPruneExpiredActive() == false) {
return 0;
}
int count = 0;
Iterator<CacheObj<K, V>> values = cacheObjIter();
CacheObj<K, V> co;
while (values.hasNext()) {
co = values.next();
if (co.isExpired()) {
values.remove();
onRemove(co.key, co.obj);
count++;
}
}
return count;
}
}
package cn.hutool.cache.impl;
import cn.hutool.cache.Cache;
import cn.hutool.core.lang.func.Func0;
import java.util.Iterator;
/**
* 无缓存实现,用于快速关闭缓存
*
* @param <K> 键类型
* @param <V> 值类型
* @author Looly,jodd
*/
public class NoCache<K, V> implements Cache<K, V> {
private static final long serialVersionUID = 1L;
@Override
public int capacity() {
return 0;
}
@Override
public long timeout() {
return 0;
}
@Override
public void put(K key, V object) {
// 跳过
}
@Override
public void put(K key, V object, long timeout) {
// 跳过
}
@Override
public boolean containsKey(K key) {
return false;
}
@Override
public V get(K key) {
return null;
}
@Override
public V get(K key, boolean isUpdateLastAccess) {
return null;
}
@Override
public V get(K key, Func0<V> supplier) {
return get(key, true, supplier);
}
@Override
public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {
try {
return (null == supplier) ? null : supplier.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Iterator<V> iterator() {
return new Iterator<V>() {
@Override
public boolean hasNext() {
return false;
}
@Override
public V next() {
return null;
}
};
}
@Override
public Iterator<CacheObj<K, V>> cacheObjIterator() {
return null;
}
@Override
public int prune() {
return 0;
}
@Override
public boolean isFull() {
return false;
}
@Override
public void remove(K key) {
// 跳过
}
@Override
public void clear() {
// 跳过
}
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
}
package cn.hutool.cache.impl;
import cn.hutool.core.collection.CopiedIter;
import java.util.Iterator;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用{@link ReentrantLock}保护的缓存,读写都使用悲观锁完成,主要避免某些Map无法使用读写锁的问题<br>
* 例如使用了LinkedHashMap的缓存,由于get方法也会改变Map的结构,因此读写必须加互斥锁
*
* @param <K> 键类型
* @param <V> 值类型
* @author looly
* @since 5.7.15
*/
public abstract class ReentrantCache<K, V> extends AbstractCache<K, V> {
private static final long serialVersionUID = 1L;
// 一些特殊缓存,例如使用了LinkedHashMap的缓存,由于get方法也会改变Map的结构,导致无法使用读写锁
// TODO 最优的解决方案是使用Guava的ConcurrentLinkedHashMap,此处使用简化的互斥锁
protected final ReentrantLock lock = new ReentrantLock();
@Override
public void put(K key, V object, long timeout) {
lock.lock();
try {
putWithoutLock(key, object, timeout);
} finally {
lock.unlock();
}
}
@Override
public boolean containsKey(K key) {
lock.lock();
try {
// 不存在或已移除
final CacheObj<K, V> co = getWithoutLock(key);
if (co == null) {
return false;
}
if (false == co.isExpired()) {
// 命中
return true;
}
} finally {
lock.unlock();
}
// 过期
remove(key, true);
return false;
}
@Override
public V get(K key, boolean isUpdateLastAccess) {
CacheObj<K, V> co;
lock.lock();
try {
co = getWithoutLock(key);
} finally {
lock.unlock();
}
// 未命中
if (null == co) {
missCount.increment();
return null;
} else if (false == co.isExpired()) {
hitCount.increment();
return co.get(isUpdateLastAccess);
}
// 过期,既不算命中也不算非命中
remove(key, true);
return null;
}
@Override
public Iterator<CacheObj<K, V>> cacheObjIterator() {
CopiedIter<CacheObj<K, V>> copiedIterator;
lock.lock();
try {
copiedIterator = CopiedIter.copyOf(cacheObjIter());
} finally {
lock.unlock();
}
return new CacheObjIterator<>(copiedIterator);
}
@Override
public final int prune() {
lock.lock();
try {
return pruneCache();
} finally {
lock.unlock();
}
}
@Override
public void remove(K key) {
remove(key, false);
}
@Override
public void clear() {
lock.lock();
try {
cacheMap.clear();
} finally {
lock.unlock();
}
}
@Override
public String toString() {
lock.lock();
try {
return super.toString();
} finally {
lock.unlock();
}
}
/**
* 移除key对应的对象
*
* @param key 键
* @param withMissCount 是否计数丢失数
*/
private void remove(K key, boolean withMissCount) {
lock.lock();
CacheObj<K, V> co;
try {
co = removeWithoutLock(key, withMissCount);
} finally {
lock.unlock();
}
if (null != co) {
onRemove(co.key, co.obj);
}
}
}
package cn.hutool.cache.impl;
import cn.hutool.core.collection.CopiedIter;
import java.util.Iterator;
import java.util.concurrent.locks.StampedLock;
/**
* 使用{@link StampedLock}保护的缓存,使用读写乐观锁
*
* @param <K> 键类型
* @param <V> 值类型
* @author looly
* @since 5.7.15
*/
public abstract class StampedCache<K, V> extends AbstractCache<K, V>{
private static final long serialVersionUID = 1L;
// 乐观锁,此处使用乐观锁解决读多写少的场景
// get时乐观读,再检查是否修改,修改则转入悲观读重新读一遍,可以有效解决在写时阻塞大量读操作的情况。
// see: https://www.cnblogs.com/jiagoushijuzi/p/13721319.html
protected final StampedLock lock = new StampedLock();
@Override
public void put(K key, V object, long timeout) {
final long stamp = lock.writeLock();
try {
putWithoutLock(key, object, timeout);
} finally {
lock.unlockWrite(stamp);
}
}
@Override
public boolean containsKey(K key) {
final long stamp = lock.readLock();
try {
// 不存在或已移除
final CacheObj<K, V> co = getWithoutLock(key);
if (co == null) {
return false;
}
if (false == co.isExpired()) {
// 命中
return true;
}
} finally {
lock.unlockRead(stamp);
}
// 过期
remove(key, true);
return false;
}
@Override
public V get(K key, boolean isUpdateLastAccess) {
// 尝试读取缓存,使用乐观读锁
long stamp = lock.tryOptimisticRead();
CacheObj<K, V> co = getWithoutLock(key);
if(false == lock.validate(stamp)){
// 有写线程修改了此对象,悲观读
stamp = lock.readLock();
try {
co = getWithoutLock(key);
} finally {
lock.unlockRead(stamp);
}
}
// 未命中
if (null == co) {
missCount.increment();
return null;
} else if (false == co.isExpired()) {
hitCount.increment();
return co.get(isUpdateLastAccess);
}
// 过期,既不算命中也不算非命中
remove(key, true);
return null;
}
@Override
public Iterator<CacheObj<K, V>> cacheObjIterator() {
CopiedIter<CacheObj<K, V>> copiedIterator;
final long stamp = lock.readLock();
try {
copiedIterator = CopiedIter.copyOf(cacheObjIter());
} finally {
lock.unlockRead(stamp);
}
return new CacheObjIterator<>(copiedIterator);
}
@Override
public final int prune() {
final long stamp = lock.writeLock();
try {
return pruneCache();
} finally {
lock.unlockWrite(stamp);
}
}
@Override
public void remove(K key) {
remove(key, false);
}
@Override
public void clear() {
final long stamp = lock.writeLock();
try {
cacheMap.clear();
} finally {
lock.unlockWrite(stamp);
}
}
/**
* 移除key对应的对象
*
* @param key 键
* @param withMissCount 是否计数丢失数
*/
private void remove(K key, boolean withMissCount) {
final long stamp = lock.writeLock();
CacheObj<K, V> co;
try {
co = removeWithoutLock(key, withMissCount);
} finally {
lock.unlockWrite(stamp);
}
if (null != co) {
onRemove(co.key, co.obj);
}
}
}
package cn.hutool.cache.impl;
import cn.hutool.cache.GlobalPruneTimer;
import cn.hutool.core.lang.mutable.Mutable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
/**
* 定时缓存<br>
* 此缓存没有容量限制,对象只有在过期后才会被移除
*
* @author Looly
*
* @param <K> 键类型
* @param <V> 值类型
*/
public class TimedCache<K, V> extends StampedCache<K, V> {
private static final long serialVersionUID = 1L;
/** 正在执行的定时任务 */
private ScheduledFuture<?> pruneJobFuture;
/**
* 构造
*
* @param timeout 超时(过期)时长,单位毫秒
*/
public TimedCache(long timeout) {
this(timeout, new HashMap<>());
}
/**
* 构造
*
* @param timeout 过期时长
* @param map 存储缓存对象的map
*/
public TimedCache(long timeout, Map<Mutable<K>, CacheObj<K, V>> map) {
this.capacity = 0;
this.timeout = timeout;
this.cacheMap = map;
}
// ---------------------------------------------------------------- prune
/**
* 清理过期对象
*
* @return 清理数
*/
@Override
protected int pruneCache() {
int count = 0;
final Iterator<CacheObj<K, V>> values = cacheObjIter();
CacheObj<K, V> co;
while (values.hasNext()) {
co = values.next();
if (co.isExpired()) {
values.remove();
onRemove(co.key, co.obj);
count++;
}
}
return count;
}
// ---------------------------------------------------------------- auto prune
/**
* 定时清理
*
* @param delay 间隔时长,单位毫秒
*/
public void schedulePrune(long delay) {
this.pruneJobFuture = GlobalPruneTimer.INSTANCE.schedule(this::prune, delay);
}
/**
* 取消定时清理
*/
public void cancelPruneSchedule() {
if (null != pruneJobFuture) {
pruneJobFuture.cancel(true);
}
}
}
package cn.hutool.cache.impl;
import cn.hutool.cache.CacheListener;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.mutable.Mutable;
import cn.hutool.core.map.WeakConcurrentMap;
import java.lang.ref.Reference;
/**
* 弱引用缓存<br>
* 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。<br>
* 丢弃某个键时,其条目从映射中有效地移除。<br>
*
* @author Looly
*
* @param <K> 键
* @param <V> 值
* @author looly
* @since 3.0.7
*/
public class WeakCache<K, V> extends TimedCache<K, V>{
private static final long serialVersionUID = 1L;
/**
* 构造
* @param timeout 超时时常,单位毫秒,-1或0表示无限制
*/
public WeakCache(long timeout) {
super(timeout, new WeakConcurrentMap<>());
}
@Override
public WeakCache<K, V> setListener(CacheListener<K, V> listener) {
super.setListener(listener);
final WeakConcurrentMap<Mutable<K>, CacheObj<K, V>> map = (WeakConcurrentMap<Mutable<K>, CacheObj<K, V>>) this.cacheMap;
// WeakKey回收之后,key对应的值已经是null了,因此此处的key也为null
map.setPurgeListener((key, value)-> listener.onRemove(Opt.ofNullable(key).map(Reference::get).map(Mutable::get).get(), value.getValue()));
return this;
}
}
/**
* 提供各种缓存实现
*
* @author looly
*
*/
package cn.hutool.cache.impl;
\ No newline at end of file
/**
* 提供简易的缓存实现,此模块参考了jodd工具中的Cache模块
*
* @author looly
*
*/
package cn.hutool.cache;
\ No newline at end of file
package cn.hutool.cache;
import cn.hutool.cache.impl.FIFOCache;
import cn.hutool.cache.impl.LRUCache;
import cn.hutool.cache.impl.WeakCache;
import cn.hutool.core.lang.Console;
import cn.hutool.core.thread.ConcurrencyTester;
import cn.hutool.core.thread.ThreadUtil;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 缓存单元测试
*
* @author looly
*
*/
public class CacheConcurrentTest {
@Test
@Ignore
public void fifoCacheTest() {
int threadCount = 4000;
final Cache<String, String> cache = new FIFOCache<>(3);
// 由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除
for (int i = 0; i < threadCount; i++) {
ThreadUtil.execute(() -> {
cache.put("key1", "value1", System.currentTimeMillis() * 3);
cache.put("key2", "value2", System.currentTimeMillis() * 3);
cache.put("key3", "value3", System.currentTimeMillis() * 3);
cache.put("key4", "value4", System.currentTimeMillis() * 3);
ThreadUtil.sleep(1000);
cache.put("key5", "value5", System.currentTimeMillis() * 3);
cache.put("key6", "value6", System.currentTimeMillis() * 3);
cache.put("key7", "value7", System.currentTimeMillis() * 3);
cache.put("key8", "value8", System.currentTimeMillis() * 3);
Console.log("put all");
});
}
for (int i = 0; i < threadCount; i++) {
ThreadUtil.execute(() -> show(cache));
}
System.out.println("==============================");
ThreadUtil.sleep(10000);
}
@Test
@Ignore
public void lruCacheTest() {
int threadCount = 40000;
final Cache<String, String> cache = new LRUCache<>(1000);
for (int i = 0; i < threadCount; i++) {
final int index = i;
ThreadUtil.execute(() -> {
cache.put("key1"+ index, "value1");
cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3);
int size = cache.size();
int capacity = cache.capacity();
if(size > capacity) {
Console.log("{} {}", size, capacity);
}
ThreadUtil.sleep(1000);
size = cache.size();
capacity = cache.capacity();
if(size > capacity) {
Console.log("## {} {}", size, capacity);
}
});
}
ThreadUtil.sleep(5000);
}
private void show(Cache<String, String> cache) {
for (Object tt : cache) {
Console.log(tt);
}
}
@Test
public void effectiveTest() {
// 模拟耗时操作消耗时间
int delay = 2000;
AtomicInteger ai = new AtomicInteger(0);
WeakCache<Integer, Integer> weakCache = new WeakCache<>(60 * 1000);
ConcurrencyTester concurrencyTester = ThreadUtil.concurrencyTest(32, () -> {
int i = ai.incrementAndGet() % 4;
weakCache.get(i, () -> {
ThreadUtil.sleep(delay);
return i;
});
});
long interval = concurrencyTester.getInterval();
// 总耗时应与单次操作耗时在同一个数量级
Assert.assertTrue(interval < delay * 2);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment