diff --git a/protocol/src/main/java/com/zfoo/protocol/model/Pair.java b/protocol/src/main/java/com/zfoo/protocol/model/Pair.java index ce4cc465d..978c92fd3 100644 --- a/protocol/src/main/java/com/zfoo/protocol/model/Pair.java +++ b/protocol/src/main/java/com/zfoo/protocol/model/Pair.java @@ -74,7 +74,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(key, value); + return Objects.hashCode(key); } @Override diff --git a/protocol/src/main/java/com/zfoo/protocol/model/PairLong.java b/protocol/src/main/java/com/zfoo/protocol/model/PairLong.java new file mode 100644 index 000000000..ac6d3975d --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/model/PairLong.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package com.zfoo.protocol.model; + + +import java.util.Objects; + +/** + * 键值对对象,只能在构造时传入键值 + * + * @param 值类型 + * @author godotg + */ +public class PairLong { + + private long key; + private V value; + + public PairLong() { + + } + + /** + * 构造 + * + * @param key 键 + * @param value 值 + */ + public PairLong(long key, V value) { + this.key = key; + this.value = value; + } + + /** + * 获取键 + * + * @return 键 + */ + public long getKey() { + return this.key; + } + + /** + * 获取值 + * + * @return 值 + */ + public V getValue() { + return this.value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PairLong pairLong = (PairLong) o; + return key == pairLong.key && Objects.equals(value, pairLong.value); + } + + @Override + public int hashCode() { + return Long.hashCode(key); + } + + @Override + public String toString() { + return "Pair [key=" + key + ", value=" + value + "]"; + } +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/util/LazyCache.java b/scheduler/src/main/java/com/zfoo/scheduler/util/LazyCache.java index 855af9b6a..c5eab22c0 100644 --- a/scheduler/src/main/java/com/zfoo/scheduler/util/LazyCache.java +++ b/scheduler/src/main/java/com/zfoo/scheduler/util/LazyCache.java @@ -1,6 +1,7 @@ package com.zfoo.scheduler.util; import com.zfoo.protocol.model.Pair; +import com.zfoo.protocol.model.PairLong; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; @@ -16,7 +17,7 @@ public class LazyCache { private static final float DEFAULT_BACK_PRESSURE_FACTOR = 0.11f; - private static final long MILLIS_MAX_SIZE_CHECK_INTERVAL = 13; + private static final long MILLIS_MAX_SIZE_CHECK_INTERVAL = 3; private static class CacheValue { public volatile V value; @@ -87,6 +88,7 @@ public void put(K key, V value) { if (oldCacheValue != null) { removeListener.accept(new Pair<>(key, oldCacheValue.value), RemovalCause.REPLACED); } + checkExpire(); checkMaximumSize(); } @@ -134,16 +136,17 @@ public int size() { // ----------------------------------------------------------------------------------------------------------------- private void checkMaximumSize() { if (cacheMap.size() > backPressureSize) { - var now = TimeUtils.currentTimeMillis(); + var currentTimeMillis = TimeUtils.currentTimeMillis(); var sizeCheckTime = sizeCheckTimeAtomic.get(); - if (now > sizeCheckTime) { - if (sizeCheckTimeAtomic.compareAndSet(sizeCheckTime, now + MILLIS_MAX_SIZE_CHECK_INTERVAL)) { + if (currentTimeMillis > sizeCheckTime) { + if (sizeCheckTimeAtomic.compareAndSet(sizeCheckTime, currentTimeMillis + MILLIS_MAX_SIZE_CHECK_INTERVAL)) { var exceedList = cacheMap.entrySet() .stream() - .sorted((a, b) -> Long.compare(a.getValue().expireTime, b.getValue().expireTime)) + .map(it -> new PairLong<>(it.getValue().expireTime, it.getKey())) + .sorted((a, b) -> Long.compare(a.getKey(), b.getKey())) .limit(Math.max(0, cacheMap.size() - maximumSize)) .toList(); - exceedList.forEach(it -> removeForCause(it.getKey(), RemovalCause.SIZE)); + exceedList.forEach(it -> removeForCause(it.getValue(), RemovalCause.SIZE)); } } } diff --git a/scheduler/src/test/java/com/zfoo/scheduler/util/LazyCacheTest.java b/scheduler/src/test/java/com/zfoo/scheduler/util/LazyCacheTest.java index b2e64dac5..da70575c5 100644 --- a/scheduler/src/test/java/com/zfoo/scheduler/util/LazyCacheTest.java +++ b/scheduler/src/test/java/com/zfoo/scheduler/util/LazyCacheTest.java @@ -5,7 +5,11 @@ import com.zfoo.protocol.util.ThreadUtils; import org.junit.Ignore; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.function.BiConsumer; /** @@ -14,25 +18,36 @@ @Ignore public class LazyCacheTest { + private static final Logger logger = LoggerFactory.getLogger(LazyCacheTest.class); + private static final BiConsumer, LazyCache.RemovalCause> myRemoveCallback = new BiConsumer, LazyCache.RemovalCause>() { @Override public void accept(Pair pair, LazyCache.RemovalCause removalCause) { - System.out.println(StringUtils.format("remove key:[{}] value:[{}] removalCause:[{}]", pair.getKey(), pair.getValue(), removalCause)); + logger.info("remove key:[{}] value:[{}] removalCause:[{}]", pair.getKey(), pair.getValue(), removalCause); } }; @Test public void putTest() { - var lazyCache = new LazyCache(3, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback); + var lazyCache = new LazyCache(10, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback); lazyCache.put(1, "a"); lazyCache.put(2, "b"); - ThreadUtils.sleep(1000); lazyCache.put(3, "c"); - ThreadUtils.sleep(1000); lazyCache.put(4, "d"); - ThreadUtils.sleep(1000); lazyCache.put(5, "e"); + lazyCache.put(6, "f"); + lazyCache.put(7, "g"); + lazyCache.put(8, "h"); + lazyCache.put(9, "i"); + lazyCache.put(10, "j"); + lazyCache.put(11, "k"); + lazyCache.put(12, "l"); + ThreadUtils.sleep(1000); + lazyCache.put(13, "m"); + ThreadUtils.sleep(1000); + lazyCache.put(14, "n"); + ThreadUtils.sleep(1000); } @Test @@ -84,4 +99,35 @@ public void batchTest() { } } } + + + @Test + public void multipleThreadTest() { + int threadNum = Runtime.getRuntime().availableProcessors() + 1; + ExecutorService[] executors = new ExecutorService[threadNum]; + for (int i = 0; i < executors.length; i++) { + executors[i] = Executors.newSingleThreadExecutor(); + } + var lazyCache = new LazyCache(1_0000, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback); + for (int i = 0; i < executors.length; i++) { + + var executor = executors[i]; + int i1 = i; + executor.execute(new Runnable() { + @Override + public void run() { + var startIndex = i1 * 1_0000; + for (int j = i1 * 1_0000; j < startIndex + 1_0000; j++) { + lazyCache.put(j, String.valueOf(j)); + } + for (int j = 0; j < 10000; j++) { + lazyCache.get(j); + ThreadUtils.sleep(1); + } + } + }); + } + ThreadUtils.sleep(Long.MAX_VALUE); + } + }