support eviction listener for Concurrent(Soft/Weak)Map

GitOrigin-RevId: 97682fca3c6db1f9ac64caced9348f11b16b4343
This commit is contained in:
Alexey Kudravtsev
2024-02-21 14:45:01 +01:00
committed by intellij-monorepo-bot
parent c705e3d84f
commit 473c6b0714
5 changed files with 47 additions and 30 deletions

View File

@@ -21,6 +21,7 @@ import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
// tests various ContainerUtil.create*, ContainerUtil.new*, CollectionFactory.create*, ConcurrentCollectionFactory.create* collections for being really weak/soft/concurrent
@RunFirst
@@ -839,4 +840,18 @@ public class ContainerUtilCollectionsTest extends Assert {
UsefulTestCase.assertEmpty(ContainerUtil.collect(map.entries().iterator()));
assertTrue(map.isEmpty());
}
@Test
public void testEvictionListenerWorks() {
AtomicReference<Object> evicted = new AtomicReference<>();
Map<Object, Object> map = CollectionFactory.createConcurrentSoftMap(value -> {
assertTrue(evicted.compareAndSet(null, value));
});
Object value = new Object();
map.put(new Object(), value);
GCUtil.tryGcSoftlyReachableObjects(() -> map.remove("")!=null/*to call processQueue()*/ || map.isEmpty());
map.remove(""); // to call processQueue()
assertSame(value, evicted.get());
}
}

View File

@@ -361,7 +361,11 @@ public final class CollectionFactory {
@Contract(value = " -> new", pure = true)
public static @NotNull <K, V> ConcurrentMap<@NotNull K, @NotNull V> createConcurrentSoftMap() {
return new ConcurrentSoftHashMap<>();
return new ConcurrentSoftHashMap<>(null);
}
@Contract(value = "_ -> new", pure = true)
public static @NotNull <K, V> ConcurrentMap<@NotNull K, @NotNull V> createConcurrentSoftMap(@NotNull Consumer<? super V> evictionListener) {
return new ConcurrentSoftHashMap<>(evictionListener);
}
@Contract(value = "_,_,_,_-> new", pure = true)

View File

@@ -8,6 +8,7 @@ import java.lang.ref.ReferenceQueue;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
/**
* Base class for concurrent (soft/weak) key:K -> strong value:V map
@@ -19,9 +20,25 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
private final ConcurrentMap<KeyReference<K>, V> myMap; // hashing strategy must be canonical, we compute corresponding hash codes using our own myHashingStrategy
private final @NotNull HashingStrategy<? super K> myHashingStrategy;
private static final float LOAD_FACTOR = 0.75f;
static final float LOAD_FACTOR = 0.75f;
static final int DEFAULT_CAPACITY = 16;
static final int DEFAULT_CONCURRENCY_LEVEL = Math.min(Runtime.getRuntime().availableProcessors(), 4);
private final Consumer<? super V> myEvictionListener;
ConcurrentRefHashMap(@Nullable Consumer<? super V> evictionListener) {
myHashingStrategy = this;
myMap = new ConcurrentHashMap<>(DEFAULT_CAPACITY, LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
myEvictionListener = evictionListener;
}
ConcurrentRefHashMap(int initialCapacity,
float loadFactor,
int concurrencyLevel,
@Nullable HashingStrategy<? super K> hashingStrategy) {
myHashingStrategy = hashingStrategy == THIS ? this : (hashingStrategy == null ? HashingStrategy.canonical() : hashingStrategy);
myMap = new ConcurrentHashMap<>(initialCapacity, loadFactor, concurrencyLevel);
myEvictionListener = null;
}
@FunctionalInterface
interface KeyReference<K> {
@@ -47,19 +64,14 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
boolean processed = false;
//noinspection unchecked
while ((wk = (KeyReference<K>)myReferenceQueue.poll()) != null) {
myMap.remove(wk);
V v = myMap.remove(wk);
if (myEvictionListener != null) {
myEvictionListener.accept(v);
}
processed = true;
}
return processed;
}
ConcurrentRefHashMap() {
this(DEFAULT_CAPACITY);
}
private ConcurrentRefHashMap(int initialCapacity) {
this(initialCapacity, LOAD_FACTOR);
}
private static final HashingStrategy<?> THIS = new HashingStrategy<Object>() {
@Override
public int hashCode(Object object) {
@@ -72,23 +84,6 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
}
};
private ConcurrentRefHashMap(int initialCapacity, float loadFactor) {
//noinspection unchecked
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, (HashingStrategy<? super K>)THIS);
}
ConcurrentRefHashMap(@NotNull HashingStrategy<? super K> hashingStrategy) {
this(DEFAULT_CAPACITY, LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, hashingStrategy);
}
ConcurrentRefHashMap(int initialCapacity,
float loadFactor,
int concurrencyLevel,
@Nullable HashingStrategy<? super K> hashingStrategy) {
myHashingStrategy = hashingStrategy == THIS ? this : (hashingStrategy == null ? HashingStrategy.canonical() : hashingStrategy);
myMap = new ConcurrentHashMap<>(initialCapacity, loadFactor, concurrencyLevel);
}
@Override
public int size() {
return entrySet().size();

View File

@@ -2,9 +2,11 @@
package com.intellij.util.containers;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.function.Consumer;
/**
* Concurrent soft key:K -> strong value:V map
@@ -13,7 +15,8 @@ import java.lang.ref.SoftReference;
* Use {@link ContainerUtil#createConcurrentSoftMap()} to create this
*/
final class ConcurrentSoftHashMap<K, V> extends ConcurrentRefHashMap<K, V> {
ConcurrentSoftHashMap() {
ConcurrentSoftHashMap(@Nullable Consumer<? super V> evictionListener) {
super(evictionListener);
}
ConcurrentSoftHashMap(int initialCapacity,

View File

@@ -24,7 +24,7 @@ final class ConcurrentWeakHashMap<K, V> extends ConcurrentRefHashMap<K, V> {
}
ConcurrentWeakHashMap(@NotNull HashingStrategy<? super K> hashingStrategy) {
super(hashingStrategy);
super(DEFAULT_CAPACITY, LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, hashingStrategy);
}
private static final class WeakKey<K> extends WeakReference<K> implements KeyReference<K> {