Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 03a7312

Browse files
committed
修复AbstrachCache.get可能造成的死锁问题(issue#4022@Github)
1 parent cb2368d commit 03a7312

File tree

2 files changed

+21
-2
lines changed

2 files changed

+21
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
* 【extra 】 修复`QLExpressEngine`allowClassSet无效问题(issue#3994@Github)
2222
* 【core 】 修复`StrBuilder`insert插入计算错误问题(issue#ICTSRZ@Gitee)
2323
* 【cron 】 修复`CronPatternUtil.nextDateAfter`计算下一个匹配表达式的日期时,计算错误问题(issue#4006@Github)
24+
* 【cach 】 修复`AbstrachCache.get`可能造成的死锁问题(issue#4022@Github)
2425

2526
-------------------------------------------------------------------------------------------------------------
2627
# 5.8.39(2025-06-20)

hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import cn.hutool.core.lang.mutable.MutableObj;
88
import cn.hutool.core.map.SafeConcurrentHashMap;
99

10+
import java.util.HashSet;
1011
import java.util.Iterator;
1112
import java.util.Map;
1213
import java.util.Set;
@@ -65,6 +66,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
6566
*/
6667
protected CacheListener<K, V> listener;
6768

69+
/**
70+
* 相同线程key缓存,用于检查key循环引用导致的死锁
71+
*/
72+
private final ThreadLocal<Set<K>> loadingKeys = ThreadLocal.withInitial(HashSet::new);
73+
6874
// ---------------------------------------------------------------- put start
6975
@Override
7076
public void put(K key, V object) {
@@ -126,6 +132,12 @@ public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {
126132
public V get(K key, boolean isUpdateLastAccess, long timeout, Func0<V> supplier) {
127133
V v = get(key, isUpdateLastAccess);
128134
if (null == v && null != supplier) {
135+
// 在尝试加锁前,检查当前线程是否已经在加载这个 key,见:issue#4022
136+
// 如果是,则说明发生了循环依赖。
137+
if (loadingKeys.get().contains(key)) {
138+
throw new IllegalStateException("Circular dependency detected for key: " + key);
139+
}
140+
129141
//每个key单独获取一把锁,降低锁的粒度提高并发能力,see pr#1385@Github
130142
final Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock());
131143
keyLock.lock();
@@ -135,9 +147,15 @@ public V get(K key, boolean isUpdateLastAccess, long timeout, Func0<V> supplier)
135147
// 因此此处需要使用带全局锁的get获取值
136148
v = get(key, isUpdateLastAccess);
137149
if (null == v) {
150+
loadingKeys.get().add(key);
138151
// supplier的创建是一个耗时过程,此处创建与全局锁无关,而与key锁相关,这样就保证每个key只创建一个value,且互斥
139-
v = supplier.callWithRuntimeException();
140-
put(key, v, timeout);
152+
try {
153+
v = supplier.callWithRuntimeException();
154+
put(key, v, timeout);
155+
} finally {
156+
// 无论 supplier 执行成功还是失败,都必须在 finally 块中移除标记
157+
loadingKeys.get().remove(key);
158+
}
141159
}
142160
} finally {
143161
keyLock.unlock();

0 commit comments

Comments
 (0)