mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
fix bug
This commit is contained in:
parent
f966fda28f
commit
3e36d7fd21
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
# 5.7.15 (2021-10-18)
|
# 5.7.15 (2021-10-19)
|
||||||
|
|
||||||
### 🐣新特性
|
### 🐣新特性
|
||||||
* 【db 】 Db.quietSetAutoCommit增加判空(issue#I4D75B@Gitee)
|
* 【db 】 Db.quietSetAutoCommit增加判空(issue#I4D75B@Gitee)
|
||||||
@ -18,6 +18,7 @@
|
|||||||
* 【poi 】 修复ExcelWriter多余调试信息导致的问题(issue#1884@Github)
|
* 【poi 】 修复ExcelWriter多余调试信息导致的问题(issue#1884@Github)
|
||||||
* 【poi 】 修复TemporalAccessorUtil.toInstant使用DateTimeFormatter导致问题(issue#1891@Github)
|
* 【poi 】 修复TemporalAccessorUtil.toInstant使用DateTimeFormatter导致问题(issue#1891@Github)
|
||||||
* 【poi 】 修复sheet.getRow(y)为null导致的问题(issue#1893@Github)
|
* 【poi 】 修复sheet.getRow(y)为null导致的问题(issue#1893@Github)
|
||||||
|
* 【cache 】 修复LRUCache线程安全问题(issue#1895@Github)
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package cn.hutool.cache.impl;
|
|||||||
|
|
||||||
import cn.hutool.cache.Cache;
|
import cn.hutool.cache.Cache;
|
||||||
import cn.hutool.cache.CacheListener;
|
import cn.hutool.cache.CacheListener;
|
||||||
import cn.hutool.core.collection.CopiedIter;
|
|
||||||
import cn.hutool.core.lang.func.Func0;
|
import cn.hutool.core.lang.func.Func0;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -12,7 +11,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.atomic.LongAdder;
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.locks.StampedLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 超时和限制大小的缓存的默认实现<br>
|
* 超时和限制大小的缓存的默认实现<br>
|
||||||
@ -31,11 +29,6 @@ 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
|
|
||||||
protected final StampedLock lock = new StampedLock();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写的时候每个key一把锁,降低锁的粒度
|
* 写的时候每个key一把锁,降低锁的粒度
|
||||||
*/
|
*/
|
||||||
@ -75,16 +68,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
put(key, object, timeout);
|
put(key, object, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void put(K key, V object, long timeout) {
|
|
||||||
final long stamp = lock.writeLock();
|
|
||||||
try {
|
|
||||||
putWithoutLock(key, object, timeout);
|
|
||||||
} finally {
|
|
||||||
lock.unlockWrite(stamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加入元素,无锁
|
* 加入元素,无锁
|
||||||
*
|
*
|
||||||
@ -93,7 +76,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
* @param timeout 超时时长
|
* @param timeout 超时时长
|
||||||
* @since 4.5.16
|
* @since 4.5.16
|
||||||
*/
|
*/
|
||||||
private void putWithoutLock(K key, V object, long timeout) {
|
protected void putWithoutLock(K key, V object, long timeout) {
|
||||||
CacheObj<K, V> co = new CacheObj<>(key, object, timeout);
|
CacheObj<K, V> co = new CacheObj<>(key, object, timeout);
|
||||||
if (timeout != 0) {
|
if (timeout != 0) {
|
||||||
existCustomTimeout = true;
|
existCustomTimeout = true;
|
||||||
@ -106,29 +89,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
// ---------------------------------------------------------------- put end
|
// ---------------------------------------------------------------- put end
|
||||||
|
|
||||||
// ---------------------------------------------------------------- get start
|
// ---------------------------------------------------------------- get start
|
||||||
@Override
|
|
||||||
public boolean containsKey(K key) {
|
|
||||||
final long stamp = lock.readLock();
|
|
||||||
try {
|
|
||||||
// 不存在或已移除
|
|
||||||
final CacheObj<K, V> co = cacheMap.get(key);
|
|
||||||
if (co == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false == co.isExpired()) {
|
|
||||||
// 命中
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.unlockRead(stamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过期
|
|
||||||
remove(key, true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 命中数
|
* @return 命中数
|
||||||
*/
|
*/
|
||||||
@ -170,36 +130,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public V get(K key, boolean isUpdateLastAccess) {
|
|
||||||
// 尝试读取缓存,使用乐观读锁
|
|
||||||
long stamp = lock.tryOptimisticRead();
|
|
||||||
CacheObj<K, V> co = cacheMap.get(key);
|
|
||||||
if(false == lock.validate(stamp)){
|
|
||||||
// 有写线程修改了此对象,悲观读
|
|
||||||
stamp = lock.readLock();
|
|
||||||
try {
|
|
||||||
co = cacheMap.get(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------- get end
|
// ---------------------------------------------------------------- get end
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -207,21 +137,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
CacheObjIterator<K, V> copiedIterator = (CacheObjIterator<K, V>) this.cacheObjIterator();
|
CacheObjIterator<K, V> copiedIterator = (CacheObjIterator<K, V>) this.cacheObjIterator();
|
||||||
return new CacheValuesIterator<>(copiedIterator);
|
return new CacheValuesIterator<>(copiedIterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<CacheObj<K, V>> cacheObjIterator() {
|
|
||||||
CopiedIter<CacheObj<K, V>> copiedIterator;
|
|
||||||
final long stamp = lock.readLock();
|
|
||||||
try {
|
|
||||||
copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
|
|
||||||
} finally {
|
|
||||||
lock.unlockRead(stamp);
|
|
||||||
}
|
|
||||||
return new CacheObjIterator<>(copiedIterator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------- prune start
|
// ---------------------------------------------------------------- prune start
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清理实现<br>
|
* 清理实现<br>
|
||||||
* 子类实现此方法时无需加锁
|
* 子类实现此方法时无需加锁
|
||||||
@ -229,16 +145,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
* @return 清理数
|
* @return 清理数
|
||||||
*/
|
*/
|
||||||
protected abstract int pruneCache();
|
protected abstract int pruneCache();
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int prune() {
|
|
||||||
final long stamp = lock.writeLock();
|
|
||||||
try {
|
|
||||||
return pruneCache();
|
|
||||||
} finally {
|
|
||||||
lock.unlockWrite(stamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------------- prune end
|
// ---------------------------------------------------------------- prune end
|
||||||
|
|
||||||
// ---------------------------------------------------------------- common start
|
// ---------------------------------------------------------------- common start
|
||||||
@ -270,21 +176,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
return (capacity > 0) && (cacheMap.size() >= capacity);
|
return (capacity > 0) && (cacheMap.size() >= capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
return cacheMap.size();
|
return cacheMap.size();
|
||||||
@ -338,25 +229,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除key对应的对象,不加锁
|
* 移除key对应的对象,不加锁
|
||||||
*
|
*
|
||||||
@ -364,7 +236,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
* @param withMissCount 是否计数丢失数
|
* @param withMissCount 是否计数丢失数
|
||||||
* @return 移除的对象,无返回null
|
* @return 移除的对象,无返回null
|
||||||
*/
|
*/
|
||||||
private CacheObj<K, V> removeWithoutLock(K key, boolean withMissCount) {
|
protected CacheObj<K, V> removeWithoutLock(K key, boolean withMissCount) {
|
||||||
final CacheObj<K, V> co = cacheMap.remove(key);
|
final CacheObj<K, V> co = cacheMap.remove(key);
|
||||||
if (withMissCount) {
|
if (withMissCount) {
|
||||||
// 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1
|
// 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1
|
||||||
|
@ -16,7 +16,7 @@ import java.util.LinkedHashMap;
|
|||||||
* @param <V> 值类型
|
* @param <V> 值类型
|
||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public class FIFOCache<K, V> extends AbstractCache<K, V> {
|
public class FIFOCache<K, V> extends StampedCache<K, V> {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,7 +15,7 @@ import java.util.Iterator;
|
|||||||
* @param <K> 键类型
|
* @param <K> 键类型
|
||||||
* @param <V> 值类型
|
* @param <V> 值类型
|
||||||
*/
|
*/
|
||||||
public class LFUCache<K, V> extends AbstractCache<K, V> {
|
public class LFUCache<K, V> extends StampedCache<K, V> {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,7 +16,7 @@ import java.util.Iterator;
|
|||||||
* @param <K> 键类型
|
* @param <K> 键类型
|
||||||
* @param <V> 值类型
|
* @param <V> 值类型
|
||||||
*/
|
*/
|
||||||
public class LRUCache<K, V> extends AbstractCache<K, V> {
|
public class LRUCache<K, V> extends ReentrantCache<K, V> {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
136
hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java
vendored
Normal file
136
hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java
vendored
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
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的结构,导致无法使用读写锁
|
||||||
|
// 最优的解决方案是使用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 = cacheMap.get(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 = cacheMap.get(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(this.cacheMap.values().iterator());
|
||||||
|
} 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
141
hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java
vendored
Normal file
141
hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java
vendored
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
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 = cacheMap.get(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 = cacheMap.get(key);
|
||||||
|
if(false == lock.validate(stamp)){
|
||||||
|
// 有写线程修改了此对象,悲观读
|
||||||
|
stamp = lock.readLock();
|
||||||
|
try {
|
||||||
|
co = cacheMap.get(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(this.cacheMap.values().iterator());
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ import java.util.concurrent.ScheduledFuture;
|
|||||||
* @param <K> 键类型
|
* @param <K> 键类型
|
||||||
* @param <V> 值类型
|
* @param <V> 值类型
|
||||||
*/
|
*/
|
||||||
public class TimedCache<K, V> extends AbstractCache<K, V> {
|
public class TimedCache<K, V> extends StampedCache<K, V> {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/** 正在执行的定时任务 */
|
/** 正在执行的定时任务 */
|
||||||
|
52
hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java
vendored
Normal file
52
hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package cn.hutool.cache;
|
||||||
|
|
||||||
|
import cn.hutool.cache.impl.LRUCache;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 见:https://github.com/dromara/hutool/issues/1895<br>
|
||||||
|
* 并发问题测试,在5.7.15前,LRUCache存在并发问题,多线程get后,map结构变更,导致null的位置不确定,
|
||||||
|
* 并可能引起死锁。
|
||||||
|
*/
|
||||||
|
public class LRUCacheTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWriteTest() throws InterruptedException {
|
||||||
|
LRUCache<Integer, Integer> cache = CacheUtil.newLRUCache(10);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
cache.put(i, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch countDownLatch = new CountDownLatch(10);
|
||||||
|
// 10个线程分别读0-9 10000次
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
int finalI = i;
|
||||||
|
new Thread(() -> {
|
||||||
|
for (int j = 0; j < 10000; j++) {
|
||||||
|
cache.get(finalI);
|
||||||
|
}
|
||||||
|
countDownLatch.countDown();
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
// 等待读线程结束
|
||||||
|
countDownLatch.await();
|
||||||
|
// 按顺序读0-9
|
||||||
|
StringBuilder sb1 = new StringBuilder();
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
sb1.append(cache.get(i));
|
||||||
|
}
|
||||||
|
Assert.assertEquals("0123456789", sb1.toString());
|
||||||
|
|
||||||
|
// 新加11,此时0最久未使用,应该淘汰0
|
||||||
|
cache.put(11, 11);
|
||||||
|
|
||||||
|
StringBuilder sb2 = new StringBuilder();
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
sb2.append(cache.get(i));
|
||||||
|
}
|
||||||
|
Assert.assertEquals("null123456789", sb2.toString());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user