ソースを参照

Merge pull request #1385 from graydovee/v5-dev

缓存降低锁的粒度,提高并发能力
Golden Looly 4 年 前
コミット
02bd117fd2

+ 14 - 3
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;
 
 /**
@@ -30,6 +33,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 	private final StampedLock lock = new StampedLock();
 
 	/**
+	 * 写的时候每个key一把锁,降低锁的粒度
+	 */
+	protected final Map<K, Lock> keyLockMap = new ConcurrentHashMap<>();
+
+	/**
 	 * 返回缓存容量,{@code 0}表示无大小限制
 	 */
 	protected int capacity;
@@ -135,7 +143,9 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 	public V get(K key, boolean isUpdateLastAccess, Func0<V> 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<K, V> co = cacheMap.get(key);
@@ -145,12 +155,13 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
 					} 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;

+ 26 - 3
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<Integer, Integer> 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);
+	}
 }