fix cache

This commit is contained in:
Looly
2021-01-24 22:21:39 +08:00
parent 02bd117fd2
commit a8add399c2
7 changed files with 45 additions and 36 deletions

View File

@@ -13,6 +13,7 @@
* 【core 】 修改上传文件检查逻辑 * 【core 】 修改上传文件检查逻辑
* 【core 】 修正LocalDateTimeUtil.offset方法注释问题issue#I2EEXC@Gitee * 【core 】 修正LocalDateTimeUtil.offset方法注释问题issue#I2EEXC@Gitee
* 【extra 】 VelocityEngine的getRowEngine改为getRawEngineissue#I2EGRG@Gitee * 【extra 】 VelocityEngine的getRowEngine改为getRawEngineissue#I2EGRG@Gitee
* 【cache 】 缓存降低锁的粒度提高并发能力pr#1385@Github
### Bug修复 ### Bug修复
* 【core 】 修复FileUtil.move以及PathUtil.copy等无法自动创建父目录的问题issue#I2CKTI@Gitee * 【core 】 修复FileUtil.move以及PathUtil.copy等无法自动创建父目录的问题issue#I2CKTI@Gitee

View File

@@ -83,7 +83,7 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
* <p> * <p>
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。 * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。
* <p> * <p>
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。 * 每次调用此方法会可选是否刷新最后访问时间,{@code true}表示会重新计算超时时间。
* *
* @param key 键 * @param key 键
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。 * @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。
@@ -96,6 +96,8 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
* 从缓存中获得对象,当对象不在缓存中或已经过期返回{@code null} * 从缓存中获得对象,当对象不在缓存中或已经过期返回{@code null}
* <p> * <p>
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。 * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回{@code null},否则返回值。
* <p>
* 每次调用此方法会可选是否刷新最后访问时间,{@code true}表示会重新计算超时时间。
* *
* @param key 键 * @param key 键
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。 * @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。

View File

@@ -30,6 +30,9 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
protected Map<K, CacheObj<K, V>> cacheMap; protected Map<K, CacheObj<K, V>> cacheMap;
// 乐观锁,此处使用乐观锁解决读多写少的场景
// get时乐观读再检查是否修改修改则转入悲观读重新读一遍可以有效解决在写时阻塞大量读操作的情况。
// see: https://www.cnblogs.com/jiagoushijuzi/p/13721319.html
private final StampedLock lock = new StampedLock(); private final StampedLock lock = new StampedLock();
/** /**
@@ -52,11 +55,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
protected boolean existCustomTimeout; protected boolean existCustomTimeout;
/** /**
* 命中数 * 命中数,即命中缓存计数
*/ */
protected AtomicLong hitCount = new AtomicLong(); protected AtomicLong hitCount = new AtomicLong();
/** /**
* 丢失数 * 丢失数,即未命中缓存计数
*/ */
protected AtomicLong missCount = new AtomicLong(); protected AtomicLong missCount = new AtomicLong();
@@ -143,8 +146,8 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) { public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {
V v = get(key, isUpdateLastAccess); V v = get(key, isUpdateLastAccess);
if (null == v && null != supplier) { if (null == v && null != supplier) {
//每个key单独获取一把锁降低锁的粒度提高并发能力 //每个key单独获取一把锁降低锁的粒度提高并发能力see pr#1385@Github
Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock()); final Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock());
keyLock.lock(); keyLock.lock();
try { try {
// 双重检查锁 // 双重检查锁
@@ -157,7 +160,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
} }
put(key, v, this.timeout); put(key, v, this.timeout);
} else { } else {
v = co.get(true); v = co.get(isUpdateLastAccess);
} }
} finally { } finally {
keyLock.unlock(); keyLock.unlock();
@@ -170,25 +173,28 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
@Override @Override
public V get(K key, boolean isUpdateLastAccess) { public V get(K key, boolean isUpdateLastAccess) {
// 尝试读取缓存,使用乐观读锁 // 尝试读取缓存,使用乐观读锁
long stamp = lock.readLock(); long stamp = lock.tryOptimisticRead();
try { CacheObj<K, V> co = cacheMap.get(key);
// 不存在或已移除 if(false == lock.validate(stamp)){
final CacheObj<K, V> co = cacheMap.get(key); // 有写线程修改了此对象,悲观读
if (null == co) { stamp = lock.readLock();
missCount.getAndIncrement(); try {
return null; co = cacheMap.get(key);
} finally {
lock.unlockRead(stamp);
} }
// 命中
if (false == co.isExpired()) {
hitCount.getAndIncrement();
return co.get(isUpdateLastAccess);
}
} finally {
lock.unlockRead(stamp);
} }
// 过期 // 未命中
if (null == co) {
missCount.getAndIncrement();
return null;
} else if (false == co.isExpired()) {
hitCount.getAndIncrement();
return co.get(isUpdateLastAccess);
}
// 过期,既不算命中也不算非命中
remove(key, true); remove(key, true);
return null; return null;
} }

View File

@@ -9,7 +9,7 @@ import java.util.Iterator;
* 使用率是通过访问次数计算的。<br> * 使用率是通过访问次数计算的。<br>
* 当缓存满时清理过期对象。<br> * 当缓存满时清理过期对象。<br>
* 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。 * 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。
* *
* @author Looly,jodd * @author Looly,jodd
* *
* @param <K> 键类型 * @param <K> 键类型
@@ -20,7 +20,7 @@ public class LFUCache<K, V> extends AbstractCache<K, V> {
/** /**
* 构造 * 构造
* *
* @param capacity 容量 * @param capacity 容量
*/ */
public LFUCache(int capacity) { public LFUCache(int capacity) {
@@ -29,7 +29,7 @@ public class LFUCache<K, V> extends AbstractCache<K, V> {
/** /**
* 构造 * 构造
* *
* @param capacity 容量 * @param capacity 容量
* @param timeout 过期时长 * @param timeout 过期时长
*/ */
@@ -37,7 +37,7 @@ public class LFUCache<K, V> extends AbstractCache<K, V> {
if(Integer.MAX_VALUE == capacity) { if(Integer.MAX_VALUE == capacity) {
capacity -= 1; capacity -= 1;
} }
this.capacity = capacity; this.capacity = capacity;
this.timeout = timeout; this.timeout = timeout;
cacheMap = new HashMap<>(capacity + 1, 1.0f); cacheMap = new HashMap<>(capacity + 1, 1.0f);
@@ -48,7 +48,7 @@ public class LFUCache<K, V> extends AbstractCache<K, V> {
/** /**
* 清理过期对象。<br> * 清理过期对象。<br>
* 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。 * 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。
* *
* @return 清理个数 * @return 清理个数
*/ */
@Override @Override
@@ -89,7 +89,7 @@ public class LFUCache<K, V> extends AbstractCache<K, V> {
} }
} }
} }
return count; return count;
} }
} }

View File

@@ -40,7 +40,7 @@ public class LRUCache<K, V> extends AbstractCache<K, V> {
this.capacity = capacity; this.capacity = capacity;
this.timeout = timeout; this.timeout = timeout;
//链表key按照访问顺序排序调用get方法后会将这次访问的元素移至头部 //链表key按照访问顺序排序调用get方法后会将这次访问的元素移至头部
cacheMap = new FixedLinkedHashMap<>(capacity); cacheMap = new FixedLinkedHashMap<>(capacity);
} }

View File

@@ -10,7 +10,7 @@ import java.util.concurrent.ScheduledFuture;
/** /**
* 定时缓存<br> * 定时缓存<br>
* 此缓存没有容量限制,对象只有在过期后才会被移除 * 此缓存没有容量限制,对象只有在过期后才会被移除
* *
* @author Looly * @author Looly
* *
* @param <K> 键类型 * @param <K> 键类型
@@ -24,7 +24,7 @@ public class TimedCache<K, V> extends AbstractCache<K, V> {
/** /**
* 构造 * 构造
* *
* @param timeout 超时(过期)时长,单位毫秒 * @param timeout 超时(过期)时长,单位毫秒
*/ */
public TimedCache(long timeout) { public TimedCache(long timeout) {
@@ -33,7 +33,7 @@ public class TimedCache<K, V> extends AbstractCache<K, V> {
/** /**
* 构造 * 构造
* *
* @param timeout 过期时长 * @param timeout 过期时长
* @param map 存储缓存对象的map * @param map 存储缓存对象的map
*/ */
@@ -46,7 +46,7 @@ public class TimedCache<K, V> extends AbstractCache<K, V> {
// ---------------------------------------------------------------- prune // ---------------------------------------------------------------- prune
/** /**
* 清理过期对象 * 清理过期对象
* *
* @return 清理数 * @return 清理数
*/ */
@Override @Override
@@ -68,7 +68,7 @@ public class TimedCache<K, V> extends AbstractCache<K, V> {
// ---------------------------------------------------------------- auto prune // ---------------------------------------------------------------- auto prune
/** /**
* 定时清理 * 定时清理
* *
* @param delay 间隔时长,单位毫秒 * @param delay 间隔时长,单位毫秒
*/ */
public void schedulePrune(long delay) { public void schedulePrune(long delay) {

View File

@@ -6,7 +6,7 @@ import java.util.WeakHashMap;
* 弱引用缓存<br> * 弱引用缓存<br>
* 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。<br> * 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。<br>
* 丢弃某个键时,其条目从映射中有效地移除。<br> * 丢弃某个键时,其条目从映射中有效地移除。<br>
* *
* @author Looly * @author Looly
* *
* @param <K> 键 * @param <K> 键
@@ -18,7 +18,7 @@ public class WeakCache<K, V> extends TimedCache<K, V>{
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public WeakCache(long timeout) { public WeakCache(long timeout) {
super(timeout, new WeakHashMap<K, CacheObj<K, V>>()); super(timeout, new WeakHashMap<>());
} }
} }