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;
}
}
}
}

View File

@ -11,6 +11,17 @@ import java.io.Serializable;
public class MutableObj<T> implements Mutable<T>, Serializable {
private static final long serialVersionUID = 1L;
/**
* 构建MutableObj
* @param value 被包装的值
* @param <T> 值类型
* @return MutableObj
* @since 5.8.0
*/
public static <T> MutableObj<T> of(T value){
return new MutableObj<>(value);
}
private T value;
/**

View File

@ -1,6 +1,9 @@
package cn.hutool.core.util;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.mutable.MutableObj;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.lang.ref.PhantomReference;
@ -31,4 +34,23 @@ public class ReferenceUtilTest {
// get方法永远都返回nullPhantomReference只能用来监控对象的GC状况
Assert.assertNull(integerReference.get());
}
@Test
@Ignore
public void gcTest(){
// https://blog.csdn.net/zmx729618/article/details/54093532
// 弱引用的对象必须使用可变对象不能使用常量对象比如String
WeakReference<MutableObj<String>> reference = new WeakReference<>(new MutableObj<>("abc"));
int i=0;
while(true){
if(reference.get()!=null){
i++;
Console.log("Object is alive for {} loops - ", i);
System.gc();
}else{
Console.log("Object has been collected.");
break;
}
}
}
}