diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java index 4269dc312..a3359fddb 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java @@ -7,7 +7,10 @@ import cn.hutool.core.lang.func.Func0; import java.util.Iterator; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.StampedLock; /** @@ -29,6 +32,11 @@ public abstract class AbstractCache implements Cache { private final StampedLock lock = new StampedLock(); + /** + * 写的时候每个key一把锁,降低锁的粒度 + */ + protected final Map keyLockMap = new ConcurrentHashMap<>(); + /** * 返回缓存容量,{@code 0}表示无大小限制 */ @@ -135,7 +143,9 @@ public abstract class AbstractCache implements Cache { public V get(K key, boolean isUpdateLastAccess, Func0 supplier) { V v = get(key, isUpdateLastAccess); if (null == v && null != supplier) { - final long stamp = lock.writeLock(); + //每个key单独获取一把锁,降低锁的粒度提高并发能力 + Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock()); + keyLock.lock(); try { // 双重检查锁 final CacheObj co = cacheMap.get(key); @@ -145,12 +155,13 @@ public abstract class AbstractCache implements Cache { } catch (Exception e) { throw new RuntimeException(e); } - putWithoutLock(key, v, this.timeout); + put(key, v, this.timeout); } else { v = co.get(true); } } finally { - lock.unlockWrite(stamp); + keyLock.unlock(); + keyLockMap.remove(key); } } return v; diff --git a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java index c3fc95fcb..c7e3bf8bf 100644 --- a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java +++ b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java @@ -3,14 +3,19 @@ package cn.hutool.cache.test; import cn.hutool.cache.Cache; import cn.hutool.cache.impl.FIFOCache; import cn.hutool.cache.impl.LRUCache; +import cn.hutool.cache.impl.WeakCache; import cn.hutool.core.lang.Console; +import cn.hutool.core.thread.ConcurrencyTester; import cn.hutool.core.thread.ThreadUtil; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.util.concurrent.atomic.AtomicInteger; + /** * 缓存单元测试 - * + * * @author looly * */ @@ -46,7 +51,7 @@ public class CacheConcurrentTest { System.out.println("=============================="); ThreadUtil.sleep(10000); } - + @Test @Ignore public void lruCacheTest() { @@ -72,7 +77,7 @@ public class CacheConcurrentTest { } }); } - + ThreadUtil.sleep(5000); } @@ -82,4 +87,22 @@ public class CacheConcurrentTest { Console.log(tt); } } + + @Test + public void effectiveTest() { + // 模拟耗时操作消耗时间 + int delay = 2000; + AtomicInteger ai = new AtomicInteger(0); + WeakCache weakCache = new WeakCache<>(60 * 1000); + ConcurrencyTester concurrencyTester = ThreadUtil.concurrencyTest(32, () -> { + int i = ai.incrementAndGet() % 4; + weakCache.get(i, () -> { + ThreadUtil.sleep(delay); + return i; + }); + }); + long interval = concurrencyTester.getInterval(); + // 总耗时应与单次操作耗时在同一个数量级 + Assert.assertTrue(interval < delay * 2); + } }