mirror of
https://gitee.com/dromara/hutool.git
synced 2025-09-21 02:58:11 +08:00
修复AbstrachCache.get
可能造成的死锁问题(issue#4022@Github)
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
* 【extra 】 修复`QLExpressEngine`allowClassSet无效问题(issue#3994@Github)
|
* 【extra 】 修复`QLExpressEngine`allowClassSet无效问题(issue#3994@Github)
|
||||||
* 【core 】 修复`StrBuilder`insert插入计算错误问题(issue#ICTSRZ@Gitee)
|
* 【core 】 修复`StrBuilder`insert插入计算错误问题(issue#ICTSRZ@Gitee)
|
||||||
* 【cron 】 修复`CronPatternUtil.nextDateAfter`计算下一个匹配表达式的日期时,计算错误问题(issue#4006@Github)
|
* 【cron 】 修复`CronPatternUtil.nextDateAfter`计算下一个匹配表达式的日期时,计算错误问题(issue#4006@Github)
|
||||||
|
* 【cach 】 修复`AbstrachCache.get`可能造成的死锁问题(issue#4022@Github)
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
# 5.8.39(2025-06-20)
|
# 5.8.39(2025-06-20)
|
||||||
|
@@ -7,6 +7,7 @@ import cn.hutool.core.lang.mutable.Mutable;
|
|||||||
import cn.hutool.core.lang.mutable.MutableObj;
|
import cn.hutool.core.lang.mutable.MutableObj;
|
||||||
import cn.hutool.core.map.SafeConcurrentHashMap;
|
import cn.hutool.core.map.SafeConcurrentHashMap;
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -65,6 +66,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
*/
|
*/
|
||||||
protected CacheListener<K, V> listener;
|
protected CacheListener<K, V> listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相同线程key缓存,用于检查key循环引用导致的死锁
|
||||||
|
*/
|
||||||
|
private final ThreadLocal<Set<K>> loadingKeys = ThreadLocal.withInitial(HashSet::new);
|
||||||
|
|
||||||
// ---------------------------------------------------------------- put start
|
// ---------------------------------------------------------------- put start
|
||||||
@Override
|
@Override
|
||||||
public void put(K key, V object) {
|
public void put(K key, V object) {
|
||||||
@@ -126,6 +132,12 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
public V get(K key, boolean isUpdateLastAccess, long timeout, Func0<V> supplier) {
|
public V get(K key, boolean isUpdateLastAccess, long timeout, Func0<V> supplier) {
|
||||||
V v = get(key, isUpdateLastAccess);
|
V v = get(key, isUpdateLastAccess);
|
||||||
if (null == v && null != supplier) {
|
if (null == v && null != supplier) {
|
||||||
|
// 在尝试加锁前,检查当前线程是否已经在加载这个 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();
|
||||||
@@ -135,9 +147,15 @@ 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 = supplier.callWithRuntimeException();
|
v = supplier.callWithRuntimeException();
|
||||||
put(key, v, timeout);
|
put(key, v, timeout);
|
||||||
|
} finally {
|
||||||
|
// 无论 supplier 执行成功还是失败,都必须在 finally 块中移除标记
|
||||||
|
loadingKeys.get().remove(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
keyLock.unlock();
|
keyLock.unlock();
|
||||||
|
Reference in New Issue
Block a user