Skip to content

Commit

Permalink
fix[cache]: Comparison method violates its general contract
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysunxiao committed Mar 27, 2024
1 parent c4812dc commit 7fcf726
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 12 deletions.
2 changes: 1 addition & 1 deletion protocol/src/main/java/com/zfoo/protocol/model/Pair.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(key, value);
return Objects.hashCode(key);
}

@Override
Expand Down
79 changes: 79 additions & 0 deletions protocol/src/main/java/com/zfoo/protocol/model/PairLong.java
Original file line number Diff line number Diff line change
@@ -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 <V> 值类型
* @author godotg
*/
public class PairLong<V> {

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 + "]";
}
}
15 changes: 9 additions & 6 deletions scheduler/src/main/java/com/zfoo/scheduler/util/LazyCache.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,7 +17,7 @@
public class LazyCache<K, V> {

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<V> {
public volatile V value;
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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));
}
}
}
Expand Down
56 changes: 51 additions & 5 deletions scheduler/src/test/java/com/zfoo/scheduler/util/LazyCacheTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -14,25 +18,36 @@
@Ignore
public class LazyCacheTest {

private static final Logger logger = LoggerFactory.getLogger(LazyCacheTest.class);

private static final BiConsumer<Pair<Integer, String>, LazyCache.RemovalCause> myRemoveCallback = new BiConsumer<Pair<Integer, String>, LazyCache.RemovalCause>() {
@Override
public void accept(Pair<Integer, String> 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<Integer, String>(3, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback);
var lazyCache = new LazyCache<Integer, String>(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
Expand Down Expand Up @@ -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<Integer, String>(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);
}

}

0 comments on commit 7fcf726

Please sign in to comment.