From 68bcc8f5de59a9fa7728259477bbd35dc6d2f40f Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 20 Jun 2025 17:34:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D`AbstractCache`putWithoutLock?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=8F=AF=E8=83=BD=E5=AF=BC=E8=87=B4=E7=9A=84?= =?UTF-8?q?=E5=A4=96=E9=83=A8=E8=B5=84=E6=BA=90=E6=B3=84=E9=9C=B2=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=88pr#3958@Github=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v7/core/cache/impl/AbstractCache.java | 4 +++- .../hutool/v7/core/cache/impl/LockedCache.java | 15 ++++++++++++++- .../core/cache/impl/TimedReentrantCache.java | 2 ++ .../cn/hutool/v7/core/cache/CacheTest.java | 18 ++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java index 3f1c20b73..eef5abb02 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java @@ -109,7 +109,9 @@ public abstract class AbstractCache implements Cache { final MutableObj mKey = MutableObj.of(key); // issue#3618 对于替换的键值对,不做满队列检查和清除 - if (cacheMap.containsKey(mKey)) { + final CacheObj oldObj = cacheMap.get(mKey); + if (null != oldObj) { + onRemove(oldObj.key, oldObj.obj); // 存在相同key,覆盖之 cacheMap.put(mKey, co); } else { diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java index 4c9542dbf..c1c73deb9 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java @@ -17,8 +17,12 @@ package cn.hutool.v7.core.cache.impl; import cn.hutool.v7.core.collection.iter.CopiedIter; +import cn.hutool.v7.core.collection.set.SetUtil; +import cn.hutool.v7.core.lang.mutable.Mutable; +import java.io.Serial; import java.util.Iterator; +import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -31,6 +35,7 @@ import java.util.concurrent.locks.ReentrantLock; * @author Looly */ public abstract class LockedCache extends AbstractCache { + @Serial private static final long serialVersionUID = 1L; /** @@ -98,7 +103,15 @@ public abstract class LockedCache extends AbstractCache { public void clear() { lock.lock(); try { - cacheMap.clear(); + // 获取所有键的副本 + final Set> keys = SetUtil.of(cacheMap.keySet()); + CacheObj co; + for (final Mutable key : keys) { + co = removeWithoutLock(key.get()); + if (null != co) { + onRemove(co.key, co.obj); // 触发资源释放 + } + } } finally { lock.unlock(); } diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/TimedReentrantCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/TimedReentrantCache.java index 6674c4ab9..a1d6d8cb3 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/TimedReentrantCache.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/TimedReentrantCache.java @@ -19,6 +19,7 @@ package cn.hutool.v7.core.cache.impl; import cn.hutool.v7.core.cache.GlobalPruneTimer; import cn.hutool.v7.core.lang.mutable.Mutable; +import java.io.Serial; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -34,6 +35,7 @@ import java.util.concurrent.ScheduledFuture; * @param 值类型 */ public class TimedReentrantCache extends LockedCache { + @Serial private static final long serialVersionUID = 1L; /** 正在执行的定时任务 */ diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/cache/CacheTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/cache/CacheTest.java index 6e13e9e81..60955b48e 100644 --- a/hutool-core/src/test/java/cn/hutool/v7/core/cache/CacheTest.java +++ b/hutool-core/src/test/java/cn/hutool/v7/core/cache/CacheTest.java @@ -164,4 +164,22 @@ public class CacheTest { assertFalse(ALARM_CACHE.containsKey(1)); assertEquals(1, counter.get()); } + + /** + * ReentrantCache类clear()方法、AbstractCache.putWithoutLock方法可能导致资源泄露 + * https://github.com/chinabugotech/hutool/issues/3957 + */ + @Test + public void reentrantCache_clear_Method_Test() { + AtomicInteger removeCount = new AtomicInteger(); + Cache lruCache = CacheUtil.newLRUCache(4); + lruCache.setListener((key, cachedObject) -> removeCount.getAndIncrement()); + lruCache.put("key1","String1"); + lruCache.put("key2","String2"); + lruCache.put("key3","String3"); + lruCache.put("key1","String4");//key已经存在,原始putWithoutLock方法存在资源泄露 + lruCache.put("key4","String5"); + lruCache.clear();//ReentrantCache类clear()方法存在资源泄露 + Assertions.assertEquals(5, removeCount.get()); + } }