mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-05 17:37:59 +08:00
fix weak bug
This commit is contained in:
parent
477657ffb8
commit
af977ac3d4
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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++;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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<>());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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方法永远都返回null,PhantomReference只能用来监控对象的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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user