fix weak bug

This commit is contained in:
Looly
2022-04-10 18:30:07 +08:00
parent 477657ffb8
commit af977ac3d4
11 changed files with 106 additions and 122 deletions

View File

@@ -3,6 +3,8 @@ 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 java.util.Iterator;
import java.util.Map;
@@ -11,6 +13,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/**
* 超时和限制大小的缓存的默认实现<br>
@@ -27,7 +30,7 @@ import java.util.concurrent.locks.ReentrantLock;
public abstract class AbstractCache<K, V> implements Cache<K, V> {
private static final long serialVersionUID = 1L;
protected Map<K, CacheObj<K, V>> cacheMap;
protected Map<Mutable<K>, CacheObj<K, V>> cacheMap;
/**
* 写的时候每个key一把锁降低锁的粒度
@@ -84,7 +87,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
if (isFull()) {
pruneCache();
}
cacheMap.put(key, co);
cacheMap.put(MutableObj.of(key), co);
}
// ---------------------------------------------------------------- put end
@@ -112,7 +115,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
keyLock.lock();
try {
// 双重检查锁,防止在竞争锁的过程中已经有其它线程写入
final CacheObj<K, V> co = cacheMap.get(key);
final CacheObj<K, V> co = getWithoutLock(key);
if (null == co || co.isExpired()) {
try {
v = supplier.call();
@@ -130,6 +133,16 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
}
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
@@ -212,7 +225,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
* @since 5.5.9
*/
public Set<K> keySet(){
return this.cacheMap.keySet();
return this.cacheMap.keySet().stream().map(Mutable::get).collect(Collectors.toSet());
}
/**
@@ -237,11 +250,20 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
* @return 移除的对象无返回null
*/
protected CacheObj<K, V> removeWithoutLock(K key, boolean withMissCount) {
final CacheObj<K, V> co = cacheMap.remove(key);
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();
}
}

View File

@@ -50,7 +50,7 @@ public class FIFOCache<K, V> extends StampedCache<K, V> {
CacheObj<K, V> first = null;
// 清理过期对象并找出链表头部元素(先入元素)
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
final Iterator<CacheObj<K, V>> values = cacheObjIter();
if (isPruneExpiredActive()) {
// 清理过期对象并找出链表头部元素(先入元素)
while (values.hasNext()) {
@@ -71,7 +71,7 @@ public class FIFOCache<K, V> extends StampedCache<K, V> {
// 清理结束后依旧是满的,则删除第一个被缓存的对象
if (isFull() && null != first) {
cacheMap.remove(first.key);
removeWithoutLock(first.key, false);
onRemove(first.key, first.obj);
count++;
}

View File

@@ -57,7 +57,7 @@ public class LFUCache<K, V> extends StampedCache<K, V> {
CacheObj<K, V> comin = null;
// 清理过期对象并找出访问最少的对象
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
Iterator<CacheObj<K, V>> values = cacheObjIter();
CacheObj<K, V> co;
while (values.hasNext()) {
co = values.next();
@@ -78,7 +78,7 @@ public class LFUCache<K, V> extends StampedCache<K, V> {
if (isFull() && comin != null) {
long minAccessCount = comin.accessCount.get();
values = cacheMap.values().iterator();
values = cacheObjIter();
CacheObj<K, V> co1;
while (values.hasNext()) {
co1 = values.next();

View File

@@ -56,7 +56,7 @@ public class LRUCache<K, V> extends ReentrantCache<K, V> {
return 0;
}
int count = 0;
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
Iterator<CacheObj<K, V>> values = cacheObjIter();
CacheObj<K, V> co;
while (values.hasNext()) {
co = values.next();

View File

@@ -36,7 +36,7 @@ public abstract class ReentrantCache<K, V> extends AbstractCache<K, V> {
lock.lock();
try {
// 不存在或已移除
final CacheObj<K, V> co = cacheMap.get(key);
final CacheObj<K, V> co = getWithoutLock(key);
if (co == null) {
return false;
}
@@ -59,7 +59,7 @@ public abstract class ReentrantCache<K, V> extends AbstractCache<K, V> {
CacheObj<K, V> co;
lock.lock();
try {
co = cacheMap.get(key);
co = getWithoutLock(key);
} finally {
lock.unlock();
}
@@ -83,7 +83,7 @@ public abstract class ReentrantCache<K, V> extends AbstractCache<K, V> {
CopiedIter<CacheObj<K, V>> copiedIterator;
lock.lock();
try {
copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
copiedIterator = CopiedIter.copyOf(cacheObjIter());
} finally {
lock.unlock();
}

View File

@@ -36,7 +36,7 @@ public abstract class StampedCache<K, V> extends AbstractCache<K, V>{
final long stamp = lock.readLock();
try {
// 不存在或已移除
final CacheObj<K, V> co = cacheMap.get(key);
final CacheObj<K, V> co = getWithoutLock(key);
if (co == null) {
return false;
}
@@ -58,12 +58,12 @@ public abstract class StampedCache<K, V> extends AbstractCache<K, V>{
public V get(K key, boolean isUpdateLastAccess) {
// 尝试读取缓存,使用乐观读锁
long stamp = lock.tryOptimisticRead();
CacheObj<K, V> co = cacheMap.get(key);
CacheObj<K, V> co = getWithoutLock(key);
if(false == lock.validate(stamp)){
// 有写线程修改了此对象,悲观读
stamp = lock.readLock();
try {
co = cacheMap.get(key);
co = getWithoutLock(key);
} finally {
lock.unlockRead(stamp);
}
@@ -88,7 +88,7 @@ public abstract class StampedCache<K, V> extends AbstractCache<K, V>{
CopiedIter<CacheObj<K, V>> copiedIterator;
final long stamp = lock.readLock();
try {
copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
copiedIterator = CopiedIter.copyOf(cacheObjIter());
} finally {
lock.unlockRead(stamp);
}

View File

@@ -1,6 +1,7 @@
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;
@@ -37,7 +38,7 @@ public class TimedCache<K, V> extends StampedCache<K, V> {
* @param timeout 过期时长
* @param map 存储缓存对象的map
*/
public TimedCache(long timeout, Map<K, CacheObj<K, V>> map) {
public TimedCache(long timeout, Map<Mutable<K>, CacheObj<K, V>> map) {
this.capacity = 0;
this.timeout = timeout;
this.cacheMap = map;
@@ -52,7 +53,7 @@ public class TimedCache<K, V> extends StampedCache<K, V> {
@Override
protected int pruneCache() {
int count = 0;
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
final Iterator<CacheObj<K, V>> values = cacheObjIter();
CacheObj<K, V> co;
while (values.hasNext()) {
co = values.next();

View File

@@ -1,10 +1,5 @@
package cn.hutool.cache.impl;
import cn.hutool.cache.Cache;
import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.lang.mutable.MutableObj;
import java.util.Iterator;
import java.util.WeakHashMap;
/**
@@ -12,112 +7,21 @@ import java.util.WeakHashMap;
* 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。<br>
* 丢弃某个键时,其条目从映射中有效地移除。<br>
*
* @param <K> 键类型
* @param <V> 值类型
* @author Looly
*
* @param <K> 键
* @param <V> 值
* @author looly
* @since 3.0.7
*/
public class WeakCache<K, V> implements Cache<K, V> {
public class WeakCache<K, V> extends TimedCache<K, V>{
private static final long serialVersionUID = 1L;
TimedCache<MutableObj<K>, V> timedCache;
/**
* 构造
*
* @param timeout 超时
* @param timeout 超时时常,单位毫秒,-1或0表示无限制
*/
public WeakCache(long timeout) {
this.timedCache = new TimedCache<>(timeout, new WeakHashMap<>());
}
@Override
public int capacity() {
return timedCache.capacity();
}
@Override
public long timeout() {
return timedCache.timeout();
}
@Override
public void put(K key, V object) {
timedCache.put(new MutableObj<>(key), object);
}
@Override
public void put(K key, V object, long timeout) {
timedCache.put(new MutableObj<>(key), object, timeout);
}
@Override
public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {
return timedCache.get(new MutableObj<>(key), isUpdateLastAccess, supplier);
}
@Override
public V get(K key, boolean isUpdateLastAccess) {
return timedCache.get(new MutableObj<>(key), isUpdateLastAccess);
}
@Override
public Iterator<CacheObj<K, V>> cacheObjIterator() {
final Iterator<CacheObj<MutableObj<K>, V>> timedIter = timedCache.cacheObjIterator();
return new Iterator<CacheObj<K, V>>() {
@Override
public boolean hasNext() {
return timedIter.hasNext();
}
@Override
public CacheObj<K, V> next() {
final CacheObj<MutableObj<K>, V> next = timedIter.next();
final CacheObj<K, V> nextNew = new CacheObj<>(next.key.get(), next.obj, next.ttl);
nextNew.lastAccess = next.lastAccess;
nextNew.accessCount = next.accessCount;
return nextNew;
}
};
}
@Override
public int prune() {
return timedCache.prune();
}
@Override
public boolean isFull() {
return timedCache.isFull();
}
@Override
public void remove(K key) {
timedCache.remove(new MutableObj<>(key));
}
@Override
public void clear() {
timedCache.clear();
}
@Override
public int size() {
return timedCache.size();
}
@Override
public boolean isEmpty() {
return timedCache.isEmpty();
}
@Override
public boolean containsKey(K key) {
return timedCache.containsKey(new MutableObj<>(key));
}
@Override
public Iterator<V> iterator() {
return timedCache.iterator();
super(timeout, new WeakHashMap<>());
}
}

View File

@@ -1,6 +1,7 @@
package cn.hutool.cache;
import cn.hutool.cache.impl.WeakCache;
import cn.hutool.core.lang.Console;
import org.junit.Assert;
import org.junit.Test;
@@ -14,8 +15,31 @@ public class WeakCacheTest {
Assert.assertEquals(2, cache.size());
// 检查被MutableObj包装的key能否正常移除
cache.remove("abc");
Assert.assertEquals(1, cache.size());
}
@Test
public void removeByGcTest(){
WeakCache<String, String> cache = new WeakCache<>(-1);
cache.put("a", "1");
cache.put("b", "2");
Assert.assertEquals(2, cache.size());
// GC测试
int i=0;
while(true){
if(2 == cache.size()){
i++;
Console.log("Object is alive for {} loops - ", i);
System.gc();
}else{
Console.log("Object has been collected.");
Console.log(cache.size());
break;
}
}
}
}