diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/cache/impl/AbstractCache.java b/hutool-core/src/main/java/org/dromara/hutool/core/cache/impl/AbstractCache.java index 5e9d7072a..c0b6e7a31 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/cache/impl/AbstractCache.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/cache/impl/AbstractCache.java @@ -43,7 +43,7 @@ public abstract class AbstractCache implements Cache { private static final long serialVersionUID = 1L; /** - * 缓存Map + * 缓存Map。子类中初始化 */ protected Map, CacheObj> cacheMap; @@ -145,12 +145,13 @@ public abstract class AbstractCache implements Cache { keyLock.lock(); try { // 双重检查锁,防止在竞争锁的过程中已经有其它线程写入 - final CacheObj co = getWithoutLock(key); - if (null == co || co.isExpired()) { + // issue#3686 由于这个方法内的加锁是get独立锁,不和put锁互斥,而put和pruneCache会修改cacheMap,导致在pruneCache过程中get会有并发问题 + // 因此此处需要使用带全局锁的get获取值 + v = get(key, isUpdateLastAccess); + if (null == v) { + // supplier的创建是一个耗时过程,此处创建与全局锁无关,而与key锁相关,这样就保证每个key只创建一个value,且互斥 v = supplier.get(); put(key, v, timeout); - } else { - v = co.get(isUpdateLastAccess); } } finally { keyLock.unlock(); diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/cache/Issue3686Test.java b/hutool-core/src/test/java/org/dromara/hutool/core/cache/Issue3686Test.java new file mode 100644 index 000000000..e0eaf677d --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/cache/Issue3686Test.java @@ -0,0 +1,23 @@ +package org.dromara.hutool.core.cache; + +public class Issue3686Test { + public static void main(final String[] args) { +// final LRUCache objects = CacheUtil.newLRUCache(20, TimeUnit.SECONDS.toMillis(30)); +// final List list = new ArrayList<>(); +// for (int i = 0; i < 10; i++) { +// final Thread thread = new Thread(() -> { +// while (true) { +// for (int i1 = 0; i1 < 100; i1++) { +// final int finalI = i1; +// objects.get((long) i1, () -> finalI); +// } +// ThreadUtil.sleep(500); +// } +// }); +// list.add(thread); +// } +// for (final Thread thread : list) { +// thread.start(); +// } + } +}