mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
support eviction listener for Concurrent(Soft/Weak)Map
GitOrigin-RevId: 97682fca3c6db1f9ac64caced9348f11b16b4343
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c705e3d84f
commit
473c6b0714
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user