Skip to content

Commit ce0323f

Browse files
committed
ConcurrentReferenceHashMap caches EntrySet in volatile field
Includes an efficient implementation of isEmpty(), not relying on a full entry count but rather backing out once a non-empty hash segment has been found. Issue: SPR-16994
1 parent 0052c89 commit ce0323f

File tree

1 file changed

+62
-50
lines changed

1 file changed

+62
-50
lines changed

spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
9898
* Late binding entry set.
9999
*/
100100
@Nullable
101-
private Set<Map.Entry<K, V>> entrySet;
101+
private volatile Set<Map.Entry<K, V>> entrySet;
102102

103103

104104
/**
@@ -167,8 +167,8 @@ public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int con
167167
* @param referenceType the reference type used for entries (soft or weak)
168168
*/
169169
@SuppressWarnings("unchecked")
170-
public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel,
171-
ReferenceType referenceType) {
170+
public ConcurrentReferenceHashMap(
171+
int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) {
172172

173173
Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative");
174174
Assert.isTrue(loadFactor > 0f, "Load factor must be positive");
@@ -215,7 +215,7 @@ protected ReferenceManager createReferenceManager() {
215215
* @return the resulting hash code
216216
*/
217217
protected int getHash(@Nullable Object o) {
218-
int hash = o == null ? 0 : o.hashCode();
218+
int hash = (o != null ? o.hashCode() : 0);
219219
hash += (hash << 15) ^ 0xffffcd7d;
220220
hash ^= (hash >>> 10);
221221
hash += (hash << 3);
@@ -247,8 +247,8 @@ public boolean containsKey(@Nullable Object key) {
247247

248248
@Nullable
249249
private Entry<K, V> getEntryIfAvailable(@Nullable Object key) {
250-
Reference<K, V> reference = getReference(key, Restructure.WHEN_NECESSARY);
251-
return (reference != null ? reference.get() : null);
250+
Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
251+
return (ref != null ? ref.get() : null);
252252
}
253253

254254
/**
@@ -281,7 +281,7 @@ private V put(@Nullable final K key, @Nullable final V value, final boolean over
281281
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
282282
@Override
283283
@Nullable
284-
protected V execute(@Nullable Reference<K, V> reference, @Nullable Entry<K, V> entry, @Nullable Entries entries) {
284+
protected V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries entries) {
285285
if (entry != null) {
286286
V oldValue = entry.getValue();
287287
if (overwriteExisting) {
@@ -302,10 +302,10 @@ public V remove(Object key) {
302302
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
303303
@Override
304304
@Nullable
305-
protected V execute(@Nullable Reference<K, V> reference, @Nullable Entry<K, V> entry) {
305+
protected V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
306306
if (entry != null) {
307-
if (reference != null) {
308-
reference.release();
307+
if (ref != null) {
308+
ref.release();
309309
}
310310
return entry.value;
311311
}
@@ -318,10 +318,10 @@ protected V execute(@Nullable Reference<K, V> reference, @Nullable Entry<K, V> e
318318
public boolean remove(Object key, final Object value) {
319319
Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
320320
@Override
321-
protected Boolean execute(@Nullable Reference<K, V> reference, @Nullable Entry<K, V> entry) {
321+
protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
322322
if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), value)) {
323-
if (reference != null) {
324-
reference.release();
323+
if (ref != null) {
324+
ref.release();
325325
}
326326
return true;
327327
}
@@ -335,7 +335,7 @@ protected Boolean execute(@Nullable Reference<K, V> reference, @Nullable Entry<K
335335
public boolean replace(K key, final V oldValue, final V newValue) {
336336
Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
337337
@Override
338-
protected Boolean execute(@Nullable Reference<K, V> reference, @Nullable Entry<K, V> entry) {
338+
protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
339339
if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), oldValue)) {
340340
entry.setValue(newValue);
341341
return true;
@@ -352,7 +352,7 @@ public V replace(K key, final V value) {
352352
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
353353
@Override
354354
@Nullable
355-
protected V execute(@Nullable Reference<K, V> reference, @Nullable Entry<K, V> entry) {
355+
protected V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
356356
if (entry != null) {
357357
V oldValue = entry.getValue();
358358
entry.setValue(value);
@@ -393,11 +393,23 @@ public int size() {
393393
}
394394

395395
@Override
396-
public Set<java.util.Map.Entry<K, V>> entrySet() {
397-
if (this.entrySet == null) {
398-
this.entrySet = new EntrySet();
396+
public boolean isEmpty() {
397+
for (Segment segment : this.segments) {
398+
if (segment.getCount() > 0) {
399+
return false;
400+
}
399401
}
400-
return this.entrySet;
402+
return true;
403+
}
404+
405+
@Override
406+
public Set<Map.Entry<K, V>> entrySet() {
407+
Set<Map.Entry<K, V>> entrySet = this.entrySet;
408+
if (entrySet == null) {
409+
entrySet = new EntrySet();
410+
this.entrySet = entrySet;
411+
}
412+
return entrySet;
401413
}
402414

403415
@Nullable
@@ -512,8 +524,8 @@ public <T> T doTask(final int hash, @Nullable final Object key, final Task<T> ta
512524
try {
513525
final int index = getIndex(hash, this.references);
514526
final Reference<K, V> head = this.references[index];
515-
Reference<K, V> reference = findInChain(head, key, hash);
516-
Entry<K, V> entry = (reference != null ? reference.get() : null);
527+
Reference<K, V> ref = findInChain(head, key, hash);
528+
Entry<K, V> entry = (ref != null ? ref.get() : null);
517529
Entries entries = new Entries() {
518530
@Override
519531
public void add(@Nullable V value) {
@@ -524,7 +536,7 @@ public void add(@Nullable V value) {
524536
Segment.this.count++;
525537
}
526538
};
527-
return task.execute(reference, entry, entries);
539+
return task.execute(ref, entry, entries);
528540
}
529541
finally {
530542
unlock();
@@ -559,19 +571,18 @@ public void clear() {
559571
* @param allowResize if resizing is permitted
560572
*/
561573
protected final void restructureIfNecessary(boolean allowResize) {
562-
boolean needsResize = ((this.count > 0) && (this.count >= this.resizeThreshold));
563-
Reference<K, V> reference = this.referenceManager.pollForPurge();
564-
if ((reference != null) || (needsResize && allowResize)) {
574+
boolean needsResize = (this.count > 0 && this.count >= this.resizeThreshold);
575+
Reference<K, V> ref = this.referenceManager.pollForPurge();
576+
if (ref != null || (needsResize && allowResize)) {
565577
lock();
566578
try {
567579
int countAfterRestructure = this.count;
568-
569580
Set<Reference<K, V>> toPurge = Collections.emptySet();
570-
if (reference != null) {
581+
if (ref != null) {
571582
toPurge = new HashSet<>();
572-
while (reference != null) {
573-
toPurge.add(reference);
574-
reference = this.referenceManager.pollForPurge();
583+
while (ref != null) {
584+
toPurge.add(ref);
585+
ref = this.referenceManager.pollForPurge();
575586
}
576587
}
577588
countAfterRestructure -= toPurge.size();
@@ -587,24 +598,25 @@ protected final void restructureIfNecessary(boolean allowResize) {
587598
}
588599

589600
// Either create a new table or reuse the existing one
590-
Reference<K, V>[] restructured = (resizing ? createReferenceArray(restructureSize) : this.references);
601+
Reference<K, V>[] restructured =
602+
(resizing ? createReferenceArray(restructureSize) : this.references);
591603

592604
// Restructure
593605
for (int i = 0; i < this.references.length; i++) {
594-
reference = this.references[i];
606+
ref = this.references[i];
595607
if (!resizing) {
596608
restructured[i] = null;
597609
}
598-
while (reference != null) {
599-
if (!toPurge.contains(reference)) {
600-
Entry<K, V> entry = reference.get();
610+
while (ref != null) {
611+
if (!toPurge.contains(ref)) {
612+
Entry<K, V> entry = ref.get();
601613
if (entry != null) {
602-
int index = getIndex(reference.getHash(), restructured);
614+
int index = getIndex(ref.getHash(), restructured);
603615
restructured[index] = this.referenceManager.createReference(
604-
entry, reference.getHash(), restructured[index]);
616+
entry, ref.getHash(), restructured[index]);
605617
}
606618
}
607-
reference = reference.getNext();
619+
ref = ref.getNext();
608620
}
609621
}
610622

@@ -622,8 +634,8 @@ protected final void restructureIfNecessary(boolean allowResize) {
622634
}
623635

624636
@Nullable
625-
private Reference<K, V> findInChain(Reference<K, V> reference, @Nullable Object key, int hash) {
626-
Reference<K, V> currRef = reference;
637+
private Reference<K, V> findInChain(Reference<K, V> ref, @Nullable Object key, int hash) {
638+
Reference<K, V> currRef = ref;
627639
while (currRef != null) {
628640
if (currRef.getHash() == hash) {
629641
Entry<K, V> entry = currRef.get();
@@ -774,26 +786,26 @@ public boolean hasOption(TaskOption option) {
774786

775787
/**
776788
* Execute the task.
777-
* @param reference the found reference or {@code null}
778-
* @param entry the found entry or {@code null}
789+
* @param ref the found reference (or {@code null})
790+
* @param entry the found entry (or {@code null})
779791
* @param entries access to the underlying entries
780792
* @return the result of the task
781793
* @see #execute(Reference, Entry)
782794
*/
783795
@Nullable
784-
protected T execute(@Nullable Reference<K, V> reference, @Nullable Entry<K, V> entry, @Nullable Entries entries) {
785-
return execute(reference, entry);
796+
protected T execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries entries) {
797+
return execute(ref, entry);
786798
}
787799

788800
/**
789801
* Convenience method that can be used for tasks that do not need access to {@link Entries}.
790-
* @param reference the found reference or {@code null}
791-
* @param entry the found entry or {@code null}
802+
* @param ref the found reference (or {@code null})
803+
* @param entry the found entry (or {@code null})
792804
* @return the result of the task
793805
* @see #execute(Reference, Entry, Entries)
794806
*/
795807
@Nullable
796-
protected T execute(@Nullable Reference<K, V> reference, @Nullable Entry<K, V> entry) {
808+
protected T execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
797809
return null;
798810
}
799811
}
@@ -834,9 +846,9 @@ public Iterator<Map.Entry<K, V>> iterator() {
834846
@Override
835847
public boolean contains(@Nullable Object o) {
836848
if (o instanceof Map.Entry<?, ?>) {
837-
Map.Entry<?, ?> entry = (java.util.Map.Entry<?, ?>) o;
838-
Reference<K, V> reference = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
839-
Entry<K, V> otherEntry = (reference != null ? reference.get() : null);
849+
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
850+
Reference<K, V> ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
851+
Entry<K, V> otherEntry = (ref != null ? ref.get() : null);
840852
if (otherEntry != null) {
841853
return ObjectUtils.nullSafeEquals(otherEntry.getValue(), otherEntry.getValue());
842854
}

0 commit comments

Comments
 (0)