From 9f7dedea9041bf0d9866bf783a0af70bf1648d29 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 25 Aug 2025 09:09:37 +0800 Subject: [PATCH] =?UTF-8?q?`ReentrantCache`=E5=A2=9E=E5=8A=A0`setUseKeyLoc?= =?UTF-8?q?k`=E8=AE=A9=E7=94=A8=E6=88=B7=E9=80=89=E6=8B=A9=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E4=BD=BF=E7=94=A8=E9=94=AE=E9=94=81=E6=9D=A5=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E6=80=A7=E8=83=BD=EF=BC=8C=E6=88=96=E8=80=85=E4=BF=9D?= =?UTF-8?q?=E8=AF=81=E5=AE=89=E5=85=A8=E3=80=82=EF=BC=88issue#4022@Github?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +- .../cn/hutool/cache/impl/ReentrantCache.java | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 315c658b5..810652f9c 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.40(2025-08-21) +# 5.8.40(2025-08-25) ### 🐣新特性 * 【captcha】 `MathGenerator`四则运算方式支持不生成负数结果(pr#1363@Gitee) @@ -21,6 +21,7 @@ * 【extra 】 修复`QLExpressEngine`allowClassSet无效问题(issue#3994@Github) * 【core 】 修复`StrBuilder`insert插入计算错误问题(issue#ICTSRZ@Gitee) * 【cron 】 修复`CronPatternUtil.nextDateAfter`计算下一个匹配表达式的日期时,计算错误问题(issue#4006@Github) +* 【cache 】 `ReentrantCache`增加`setUseKeyLock`让用户选择是否使用键锁来增强性能,或者保证安全。(issue#4022@Github) ------------------------------------------------------------------------------------------------------------- # 5.8.39(2025-06-20) diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java index 5775d266d..5f6a6b9a8 100755 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java @@ -1,6 +1,7 @@ package cn.hutool.cache.impl; import cn.hutool.core.collection.CopiedIter; +import cn.hutool.core.lang.func.Func0; import cn.hutool.core.lang.mutable.Mutable; import java.util.HashSet; @@ -24,6 +25,20 @@ public abstract class ReentrantCache extends AbstractCache { // TODO 最优的解决方案是使用Guava的ConcurrentLinkedHashMap,此处使用简化的互斥锁 protected final ReentrantLock lock = new ReentrantLock(); + private boolean useKeyLock = true; + + /** + * 设置是否使用key锁,默认为true + * + * @param useKeyLock 是否使用key锁 + * @return this + * @since 5.8.40 + */ + public ReentrantCache setUseKeyLock(boolean useKeyLock) { + this.useKeyLock = useKeyLock; + return this; + } + @Override public void put(K key, V object, long timeout) { lock.lock(); @@ -44,6 +59,36 @@ public abstract class ReentrantCache extends AbstractCache { return getOrRemoveExpired(key, isUpdateLastAccess, true); } + @Override + public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final Func0 valueFactory) { + if(useKeyLock){ + // 用户如果允许,则使用key锁,可以减少valueFactory对象创建造成的 + return super.get(key, isUpdateLastAccess, timeout, valueFactory); + } + + V v = get(key, isUpdateLastAccess); + + // 对象不存在,则加锁创建 + if (null == v && null != valueFactory) { + // 按照pr#1385提议,使用key锁可以避免对象创建等待问题,但是会带来循环锁问题,见:issue#4022 + // 因此此处依旧采用全局锁,在对象创建过程中,全局等待,避免循环锁依赖 + // 这样避免了循环锁,但是会存在一个缺点,即对象创建过程中,其它线程无法获得锁,从而无法使用缓存,因此需要考虑对象创建的耗时问题 + lock.lock(); + try { + // 双重检查锁,防止在竞争锁的过程中已经有其它线程写入 + final CacheObj co = getWithoutLock(key); + if (null == co) { + // supplier的创建是一个耗时过程,此处创建与全局锁无关,而与key锁相关,这样就保证每个key只创建一个value,且互斥 + v = valueFactory.callWithRuntimeException(); + put(key, v, timeout); + } + } finally { + lock.unlock(); + } + } + return v; + } + @Override public Iterator> cacheObjIterator() { CopiedIter> copiedIterator;