7
7
import cn .hutool .core .lang .mutable .MutableObj ;
8
8
import cn .hutool .core .map .SafeConcurrentHashMap ;
9
9
10
+ import java .util .HashSet ;
10
11
import java .util .Iterator ;
11
12
import java .util .Map ;
12
13
import java .util .Set ;
@@ -65,6 +66,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
65
66
*/
66
67
protected CacheListener <K , V > listener ;
67
68
69
+ /**
70
+ * 相同线程key缓存,用于检查key循环引用导致的死锁
71
+ */
72
+ private final ThreadLocal <Set <K >> loadingKeys = ThreadLocal .withInitial (HashSet ::new );
73
+
68
74
// ---------------------------------------------------------------- put start
69
75
@ Override
70
76
public void put (K key , V object ) {
@@ -126,6 +132,12 @@ public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {
126
132
public V get (K key , boolean isUpdateLastAccess , long timeout , Func0 <V > supplier ) {
127
133
V v = get (key , isUpdateLastAccess );
128
134
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
+
129
141
//每个key单独获取一把锁,降低锁的粒度提高并发能力,see pr#1385@Github
130
142
final Lock keyLock = keyLockMap .computeIfAbsent (key , k -> new ReentrantLock ());
131
143
keyLock .lock ();
@@ -135,9 +147,15 @@ public V get(K key, boolean isUpdateLastAccess, long timeout, Func0<V> supplier)
135
147
// 因此此处需要使用带全局锁的get获取值
136
148
v = get (key , isUpdateLastAccess );
137
149
if (null == v ) {
150
+ loadingKeys .get ().add (key );
138
151
// 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
+ }
141
159
}
142
160
} finally {
143
161
keyLock .unlock ();
0 commit comments