mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-12-06 17:18:54 +08:00
fix cache
This commit is contained in:
parent
814b26de01
commit
fc72a84d07
@ -85,11 +85,11 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
|
||||
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
|
||||
*
|
||||
* @param key 键
|
||||
* @param supplier 如果不存在回调方法,用于生产值对象
|
||||
* @param valueFactory 如果不存在回调方法,用于生产值对象
|
||||
* @return 值对象
|
||||
*/
|
||||
default V get(final K key, final SerSupplier<V> supplier) {
|
||||
return get(key, true, supplier);
|
||||
default V get(final K key, final SerSupplier<V> valueFactory) {
|
||||
return get(key, true, valueFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,10 +99,12 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
|
||||
*
|
||||
* @param key 键
|
||||
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。
|
||||
* @param supplier 如果不存在回调方法,用于生产值对象
|
||||
* @param valueFactory 如果不存在回调方法,用于生产值对象
|
||||
* @return 值对象
|
||||
*/
|
||||
V get(K key, boolean isUpdateLastAccess, SerSupplier<V> supplier);
|
||||
default V get(final K key, final boolean isUpdateLastAccess, final SerSupplier<V> valueFactory) {
|
||||
return get(key, isUpdateLastAccess, timeout(), valueFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期(与当前时间差值大于超时时间)返回SerSupplier回调产生的对象,否则返回值。
|
||||
@ -112,10 +114,10 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
|
||||
* @param key 键
|
||||
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。
|
||||
* @param timeout 自定义超时时间
|
||||
* @param supplier 如果不存在回调方法,用于生产值对象
|
||||
* @param valueFactory 如果不存在回调方法,用于生产值对象
|
||||
* @return 值对象
|
||||
*/
|
||||
V get(K key, boolean isUpdateLastAccess, final long timeout, SerSupplier<V> supplier);
|
||||
V get(K key, boolean isUpdateLastAccess, final long timeout, SerSupplier<V> valueFactory);
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期(与当前时间差值大于超时时间)返回{@code null},否则返回值。
|
||||
|
||||
@ -23,7 +23,6 @@ import cn.hutool.v7.core.lang.mutable.Mutable;
|
||||
import cn.hutool.v7.core.lang.mutable.MutableObj;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@ -141,24 +140,9 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(final K key, final boolean isUpdateLastAccess, final SerSupplier<V> supplier) {
|
||||
return get(key, isUpdateLastAccess, this.timeout, supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 相同线程key缓存,用于检查key循环引用导致的死锁
|
||||
*/
|
||||
private final ThreadLocal<Set<K>> loadingKeys = ThreadLocal.withInitial(HashSet::new);
|
||||
@Override
|
||||
public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final SerSupplier<V> supplier) {
|
||||
public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final SerSupplier<V> valueFactory) {
|
||||
V v = get(key, isUpdateLastAccess);
|
||||
if (null == v && null != supplier) {
|
||||
// 在尝试加锁前,检查当前线程是否已经在加载这个 key,见:issue#4022
|
||||
// 如果是,则说明发生了循环依赖。
|
||||
if (loadingKeys.get().contains(key)) {
|
||||
throw new IllegalStateException("Circular dependency detected for key: " + key);
|
||||
}
|
||||
|
||||
if (null == v && null != valueFactory) {
|
||||
//每个key单独获取一把锁,降低锁的粒度提高并发能力,see pr#1385@Github
|
||||
final Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock());
|
||||
keyLock.lock();
|
||||
@ -168,15 +152,9 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
// 因此此处需要使用带全局锁的get获取值
|
||||
v = get(key, isUpdateLastAccess);
|
||||
if (null == v) {
|
||||
loadingKeys.get().add(key);
|
||||
// supplier的创建是一个耗时过程,此处创建与全局锁无关,而与key锁相关,这样就保证每个key只创建一个value,且互斥
|
||||
try {
|
||||
v = supplier.get();
|
||||
put(key, v, timeout);
|
||||
} finally {
|
||||
// 无论 supplier 执行成功还是失败,都必须在 finally 块中移除标记
|
||||
loadingKeys.get().remove(key);
|
||||
}
|
||||
v = valueFactory.get();
|
||||
put(key, v, timeout);
|
||||
}
|
||||
} finally {
|
||||
keyLock.unlock();
|
||||
@ -226,7 +204,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
*/
|
||||
@Override
|
||||
public long timeout() {
|
||||
return timeout;
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -19,6 +19,7 @@ package cn.hutool.v7.core.cache.impl;
|
||||
import cn.hutool.v7.core.cache.Cache;
|
||||
import cn.hutool.v7.core.func.SerSupplier;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
@ -29,6 +30,7 @@ import java.util.Iterator;
|
||||
* @author Looly, jodd, VampireAchao
|
||||
*/
|
||||
public class NoCache<K, V> implements Cache<K, V> {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@ -67,18 +69,8 @@ public class NoCache<K, V> implements Cache<K, V> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(final K key, final SerSupplier<V> supplier) {
|
||||
return get(key, true, supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(final K key, final boolean isUpdateLastAccess, final SerSupplier<V> supplier) {
|
||||
return get(key, isUpdateLastAccess, 0, supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final SerSupplier<V> supplier) {
|
||||
return (null == supplier) ? null : supplier.get();
|
||||
public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final SerSupplier<V> valueFactory) {
|
||||
return (null == valueFactory) ? null : valueFactory.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -19,6 +19,8 @@ package cn.hutool.v7.core.cache;
|
||||
import cn.hutool.v7.core.cache.impl.FIFOCache;
|
||||
import cn.hutool.v7.core.cache.impl.LRUCache;
|
||||
import cn.hutool.v7.core.cache.impl.WeakCache;
|
||||
import cn.hutool.v7.core.exception.HutoolException;
|
||||
import cn.hutool.v7.core.func.SerSupplier;
|
||||
import cn.hutool.v7.core.lang.Console;
|
||||
import cn.hutool.v7.core.thread.ConcurrencyTester;
|
||||
import cn.hutool.v7.core.thread.ThreadUtil;
|
||||
@ -26,6 +28,10 @@ import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
@ -121,4 +127,43 @@ public class CacheConcurrentTest {
|
||||
// 总耗时应与单次操作耗时在同一个数量级
|
||||
Assertions.assertTrue(interval < delay * 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void issue4022Test() throws InterruptedException {
|
||||
final Cache<String, String> cache = new LRUCache<>(100);
|
||||
|
||||
final String key1 = "key1";
|
||||
final String key2 = "key2";
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
// 线程1:key1 -> key2
|
||||
ThreadUtil.execute(() -> {
|
||||
try {
|
||||
final String result = cache.get(key1, () -> {
|
||||
Thread.sleep(100);
|
||||
return cache.get(key2, () -> "value2") + "-nested";
|
||||
});
|
||||
}
|
||||
finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
// 线程2:key2 -> key1
|
||||
ThreadUtil.execute(() -> {
|
||||
try {
|
||||
final String result = cache.get(key2, () -> {
|
||||
Thread.sleep(100);
|
||||
return cache.get(key1, () -> "value1") + "-nested";
|
||||
});
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
// 设置超时检测死锁
|
||||
if (!latch.await(5, TimeUnit.SECONDS)) {
|
||||
throw new HutoolException("检测到可能的死锁!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user