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