Make ConcurrentReferenceHashMap more JIT friendly
Update `ConcurrentReferenceHashMap` to make some methods more inline friendly, and to manually inline a few others. These minor optimizations don't make a great deal of difference for most applications, but seem worthwhile since we use `ConcurrentReferenceHashMap` for many internal caches. Closes gh-22566
This commit is contained in:
parent
aa4e56b251
commit
e3a86be122
|
|
@ -35,6 +35,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.ConcurrentReferenceHashMap.Reference;
|
||||||
|
import org.springframework.util.ConcurrentReferenceHashMap.Restructure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or
|
* A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or
|
||||||
|
|
@ -232,27 +234,24 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public V get(@Nullable Object key) {
|
public V get(@Nullable Object key) {
|
||||||
Entry<K, V> entry = getEntryIfAvailable(key);
|
Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
|
||||||
|
Entry<K, V> entry = (ref != null ? ref.get() : null);
|
||||||
return (entry != null ? entry.getValue() : null);
|
return (entry != null ? entry.getValue() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public V getOrDefault(@Nullable Object key, @Nullable V defaultValue) {
|
public V getOrDefault(@Nullable Object key, @Nullable V defaultValue) {
|
||||||
Entry<K, V> entry = getEntryIfAvailable(key);
|
Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
|
||||||
|
Entry<K, V> entry = (ref != null ? ref.get() : null);
|
||||||
return (entry != null ? entry.getValue() : defaultValue);
|
return (entry != null ? entry.getValue() : defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsKey(@Nullable Object key) {
|
public boolean containsKey(@Nullable Object key) {
|
||||||
Entry<K, V> entry = getEntryIfAvailable(key);
|
|
||||||
return (entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private Entry<K, V> getEntryIfAvailable(@Nullable Object key) {
|
|
||||||
Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
|
Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
|
||||||
return (ref != null ? ref.get() : null);
|
Entry<K, V> entry = (ref != null ? ref.get() : null);
|
||||||
|
return (entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -573,65 +572,70 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||||
*/
|
*/
|
||||||
protected final void restructureIfNecessary(boolean allowResize) {
|
protected final void restructureIfNecessary(boolean allowResize) {
|
||||||
int currCount = this.count.get();
|
int currCount = this.count.get();
|
||||||
boolean needsResize = (currCount > 0 && currCount >= this.resizeThreshold);
|
boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold);
|
||||||
Reference<K, V> ref = this.referenceManager.pollForPurge();
|
Reference<K, V> ref = this.referenceManager.pollForPurge();
|
||||||
if (ref != null || (needsResize && allowResize)) {
|
if (ref != null || (needsResize)) {
|
||||||
lock();
|
restructure(allowResize, ref);
|
||||||
try {
|
}
|
||||||
int countAfterRestructure = this.count.get();
|
}
|
||||||
Set<Reference<K, V>> toPurge = Collections.emptySet();
|
|
||||||
if (ref != null) {
|
private void restructure(boolean allowResize, Reference<K, V> ref) {
|
||||||
toPurge = new HashSet<>();
|
boolean needsResize;
|
||||||
while (ref != null) {
|
lock();
|
||||||
toPurge.add(ref);
|
try {
|
||||||
ref = this.referenceManager.pollForPurge();
|
int countAfterRestructure = this.count.get();
|
||||||
}
|
Set<Reference<K, V>> toPurge = Collections.emptySet();
|
||||||
|
if (ref != null) {
|
||||||
|
toPurge = new HashSet<>();
|
||||||
|
while (ref != null) {
|
||||||
|
toPurge.add(ref);
|
||||||
|
ref = this.referenceManager.pollForPurge();
|
||||||
}
|
}
|
||||||
countAfterRestructure -= toPurge.size();
|
}
|
||||||
|
countAfterRestructure -= toPurge.size();
|
||||||
|
|
||||||
// Recalculate taking into account count inside lock and items that
|
// Recalculate taking into account count inside lock and items that
|
||||||
// will be purged
|
// will be purged
|
||||||
needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
|
needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
|
||||||
boolean resizing = false;
|
boolean resizing = false;
|
||||||
int restructureSize = this.references.length;
|
int restructureSize = this.references.length;
|
||||||
if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
|
if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
|
||||||
restructureSize <<= 1;
|
restructureSize <<= 1;
|
||||||
resizing = true;
|
resizing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either create a new table or reuse the existing one
|
||||||
|
Reference<K, V>[] restructured =
|
||||||
|
(resizing ? createReferenceArray(restructureSize) : this.references);
|
||||||
|
|
||||||
|
// Restructure
|
||||||
|
for (int i = 0; i < this.references.length; i++) {
|
||||||
|
ref = this.references[i];
|
||||||
|
if (!resizing) {
|
||||||
|
restructured[i] = null;
|
||||||
}
|
}
|
||||||
|
while (ref != null) {
|
||||||
// Either create a new table or reuse the existing one
|
if (!toPurge.contains(ref)) {
|
||||||
Reference<K, V>[] restructured =
|
Entry<K, V> entry = ref.get();
|
||||||
(resizing ? createReferenceArray(restructureSize) : this.references);
|
if (entry != null) {
|
||||||
|
int index = getIndex(ref.getHash(), restructured);
|
||||||
// Restructure
|
restructured[index] = this.referenceManager.createReference(
|
||||||
for (int i = 0; i < this.references.length; i++) {
|
entry, ref.getHash(), restructured[index]);
|
||||||
ref = this.references[i];
|
|
||||||
if (!resizing) {
|
|
||||||
restructured[i] = null;
|
|
||||||
}
|
|
||||||
while (ref != null) {
|
|
||||||
if (!toPurge.contains(ref)) {
|
|
||||||
Entry<K, V> entry = ref.get();
|
|
||||||
if (entry != null) {
|
|
||||||
int index = getIndex(ref.getHash(), restructured);
|
|
||||||
restructured[index] = this.referenceManager.createReference(
|
|
||||||
entry, ref.getHash(), restructured[index]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ref = ref.getNext();
|
|
||||||
}
|
}
|
||||||
|
ref = ref.getNext();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Replace volatile members
|
// Replace volatile members
|
||||||
if (resizing) {
|
if (resizing) {
|
||||||
this.references = restructured;
|
this.references = restructured;
|
||||||
this.resizeThreshold = (int) (this.references.length * getLoadFactor());
|
this.resizeThreshold = (int) (this.references.length * getLoadFactor());
|
||||||
}
|
|
||||||
this.count.set(Math.max(countAfterRestructure, 0));
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
unlock();
|
|
||||||
}
|
}
|
||||||
|
this.count.set(Math.max(countAfterRestructure, 0));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue