Skip to content

Commit 44b2657

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 9f69638 commit 44b2657

File tree

1 file changed

+65
-53
lines changed

1 file changed

+65
-53
lines changed

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

Lines changed: 65 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
* {@linkplain SoftReference soft entry references}.
5353
*
5454
* @author Phillip Webb
55+
* @author Juergen Hoeller
5556
* @since 3.2
5657
* @param <K> the key type
5758
* @param <V> the value type
@@ -94,7 +95,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
9495
/**
9596
* Late binding entry set.
9697
*/
97-
private Set<Map.Entry<K, V>> entrySet;
98+
private volatile Set<Map.Entry<K, V>> entrySet;
9899

99100

100101
/**
@@ -163,8 +164,8 @@ public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int con
163164
* @param referenceType the reference type used for entries (soft or weak)
164165
*/
165166
@SuppressWarnings("unchecked")
166-
public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel,
167-
ReferenceType referenceType) {
167+
public ConcurrentReferenceHashMap(
168+
int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) {
168169

169170
Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative");
170171
Assert.isTrue(loadFactor > 0f, "Load factor must be positive");
@@ -211,7 +212,7 @@ protected ReferenceManager createReferenceManager() {
211212
* @return the resulting hash code
212213
*/
213214
protected int getHash(Object o) {
214-
int hash = o == null ? 0 : o.hashCode();
215+
int hash = (o != null ? o.hashCode() : 0);
215216
hash += (hash << 15) ^ 0xffffcd7d;
216217
hash ^= (hash >>> 10);
217218
hash += (hash << 3);
@@ -240,8 +241,8 @@ public boolean containsKey(Object key) {
240241
}
241242

242243
private Entry<K, V> getEntryIfAvailable(Object key) {
243-
Reference<K, V> reference = getReference(key, Restructure.WHEN_NECESSARY);
244-
return (reference != null ? reference.get() : null);
244+
Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
245+
return (ref != null ? ref.get() : null);
245246
}
246247

247248
/**
@@ -269,13 +270,13 @@ public V putIfAbsent(K key, V value) {
269270
private V put(final K key, final V value, final boolean overwriteExisting) {
270271
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
271272
@Override
272-
protected V execute(Reference<K, V> reference, Entry<K, V> entry, Entries entries) {
273+
protected V execute(Reference<K, V> ref, Entry<K, V> entry, Entries entries) {
273274
if (entry != null) {
274-
V previousValue = entry.getValue();
275+
V oldValue = entry.getValue();
275276
if (overwriteExisting) {
276277
entry.setValue(value);
277278
}
278-
return previousValue;
279+
return oldValue;
279280
}
280281
entries.add(value);
281282
return null;
@@ -287,9 +288,9 @@ protected V execute(Reference<K, V> reference, Entry<K, V> entry, Entries entrie
287288
public V remove(Object key) {
288289
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
289290
@Override
290-
protected V execute(Reference<K, V> reference, Entry<K, V> entry) {
291+
protected V execute(Reference<K, V> ref, Entry<K, V> entry) {
291292
if (entry != null) {
292-
reference.release();
293+
ref.release();
293294
return entry.value;
294295
}
295296
return null;
@@ -301,9 +302,9 @@ protected V execute(Reference<K, V> reference, Entry<K, V> entry) {
301302
public boolean remove(Object key, final Object value) {
302303
return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
303304
@Override
304-
protected Boolean execute(Reference<K, V> reference, Entry<K, V> entry) {
305+
protected Boolean execute(Reference<K, V> ref, Entry<K, V> entry) {
305306
if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), value)) {
306-
reference.release();
307+
ref.release();
307308
return true;
308309
}
309310
return false;
@@ -315,7 +316,7 @@ protected Boolean execute(Reference<K, V> reference, Entry<K, V> entry) {
315316
public boolean replace(K key, final V oldValue, final V newValue) {
316317
return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
317318
@Override
318-
protected Boolean execute(Reference<K, V> reference, Entry<K, V> entry) {
319+
protected Boolean execute(Reference<K, V> ref, Entry<K, V> entry) {
319320
if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), oldValue)) {
320321
entry.setValue(newValue);
321322
return true;
@@ -329,11 +330,11 @@ protected Boolean execute(Reference<K, V> reference, Entry<K, V> entry) {
329330
public V replace(K key, final V value) {
330331
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
331332
@Override
332-
protected V execute(Reference<K, V> reference, Entry<K, V> entry) {
333+
protected V execute(Reference<K, V> ref, Entry<K, V> entry) {
333334
if (entry != null) {
334-
V previousValue = entry.getValue();
335+
V oldValue = entry.getValue();
335336
entry.setValue(value);
336-
return previousValue;
337+
return oldValue;
337338
}
338339
return null;
339340
}
@@ -370,11 +371,23 @@ public int size() {
370371
}
371372

372373
@Override
373-
public Set<java.util.Map.Entry<K, V>> entrySet() {
374-
if (this.entrySet == null) {
375-
this.entrySet = new EntrySet();
374+
public boolean isEmpty() {
375+
for (Segment segment : this.segments) {
376+
if (segment.getCount() > 0) {
377+
return false;
378+
}
379+
}
380+
return true;
381+
}
382+
383+
@Override
384+
public Set<Map.Entry<K, V>> entrySet() {
385+
Set<Map.Entry<K, V>> entrySet = this.entrySet;
386+
if (entrySet == null) {
387+
entrySet = new EntrySet();
388+
this.entrySet = entrySet;
376389
}
377-
return this.entrySet;
390+
return entrySet;
378391
}
379392

380393
private <T> T doTask(Object key, Task<T> task) {
@@ -485,8 +498,8 @@ public <T> T doTask(final int hash, final Object key, final Task<T> task) {
485498
try {
486499
final int index = getIndex(hash, this.references);
487500
final Reference<K, V> head = this.references[index];
488-
Reference<K, V> reference = findInChain(head, key, hash);
489-
Entry<K, V> entry = (reference != null ? reference.get() : null);
501+
Reference<K, V> ref = findInChain(head, key, hash);
502+
Entry<K, V> entry = (ref != null ? ref.get() : null);
490503
Entries entries = new Entries() {
491504
@Override
492505
public void add(V value) {
@@ -497,7 +510,7 @@ public void add(V value) {
497510
Segment.this.count++;
498511
}
499512
};
500-
return task.execute(reference, entry, entries);
513+
return task.execute(ref, entry, entries);
501514
}
502515
finally {
503516
unlock();
@@ -531,19 +544,18 @@ public void clear() {
531544
* @param allowResize if resizing is permitted
532545
*/
533546
protected final void restructureIfNecessary(boolean allowResize) {
534-
boolean needsResize = ((this.count > 0) && (this.count >= this.resizeThreshold));
535-
Reference<K, V> reference = this.referenceManager.pollForPurge();
536-
if ((reference != null) || (needsResize && allowResize)) {
547+
boolean needsResize = (this.count > 0 && this.count >= this.resizeThreshold);
548+
Reference<K, V> ref = this.referenceManager.pollForPurge();
549+
if (ref != null || (needsResize && allowResize)) {
537550
lock();
538551
try {
539552
int countAfterRestructure = this.count;
540-
541553
Set<Reference<K, V>> toPurge = Collections.emptySet();
542-
if (reference != null) {
554+
if (ref != null) {
543555
toPurge = new HashSet<Reference<K, V>>();
544-
while (reference != null) {
545-
toPurge.add(reference);
546-
reference = this.referenceManager.pollForPurge();
556+
while (ref != null) {
557+
toPurge.add(ref);
558+
ref = this.referenceManager.pollForPurge();
547559
}
548560
}
549561
countAfterRestructure -= toPurge.size();
@@ -559,22 +571,22 @@ protected final void restructureIfNecessary(boolean allowResize) {
559571
}
560572

561573
// Either create a new table or reuse the existing one
562-
Reference<K, V>[] restructured = (resizing ? createReferenceArray(restructureSize) : this.references);
574+
Reference<K, V>[] restructured =
575+
(resizing ? createReferenceArray(restructureSize) : this.references);
563576

564577
// Restructure
565578
for (int i = 0; i < this.references.length; i++) {
566-
reference = this.references[i];
579+
ref = this.references[i];
567580
if (!resizing) {
568581
restructured[i] = null;
569582
}
570-
while (reference != null) {
571-
if (!toPurge.contains(reference) && (reference.get() != null)) {
572-
int index = getIndex(reference.getHash(), restructured);
583+
while (ref != null) {
584+
if (!toPurge.contains(ref) && (ref.get() != null)) {
585+
int index = getIndex(ref.getHash(), restructured);
573586
restructured[index] = this.referenceManager.createReference(
574-
reference.get(), reference.getHash(),
575-
restructured[index]);
587+
ref.get(), ref.getHash(), restructured[index]);
576588
}
577-
reference = reference.getNext();
589+
ref = ref.getNext();
578590
}
579591
}
580592

@@ -590,8 +602,8 @@ protected final void restructureIfNecessary(boolean allowResize) {
590602
}
591603
}
592604

593-
private Reference<K, V> findInChain(Reference<K, V> reference, Object key, int hash) {
594-
Reference<K, V> currRef = reference;
605+
private Reference<K, V> findInChain(Reference<K, V> ref, Object key, int hash) {
606+
Reference<K, V> currRef = ref;
595607
while (currRef != null) {
596608
if (currRef.getHash() == hash) {
597609
Entry<K, V> entry = currRef.get();
@@ -744,24 +756,24 @@ public boolean hasOption(TaskOption option) {
744756

745757
/**
746758
* Execute the task.
747-
* @param reference the found reference or {@code null}
759+
* @param ref the found reference or {@code null}
748760
* @param entry the found entry or {@code null}
749761
* @param entries access to the underlying entries
750762
* @return the result of the task
751763
* @see #execute(Reference, Entry)
752764
*/
753-
protected T execute(Reference<K, V> reference, Entry<K, V> entry, Entries entries) {
754-
return execute(reference, entry);
765+
protected T execute(Reference<K, V> ref, Entry<K, V> entry, Entries entries) {
766+
return execute(ref, entry);
755767
}
756768

757769
/**
758770
* Convenience method that can be used for tasks that do not need access to {@link Entries}.
759-
* @param reference the found reference or {@code null}
771+
* @param ref the found reference or {@code null}
760772
* @param entry the found entry or {@code null}
761773
* @return the result of the task
762774
* @see #execute(Reference, Entry, Entries)
763775
*/
764-
protected T execute(Reference<K, V> reference, Entry<K, V> entry) {
776+
protected T execute(Reference<K, V> ref, Entry<K, V> entry) {
765777
return null;
766778
}
767779
}
@@ -801,12 +813,12 @@ public Iterator<Map.Entry<K, V>> iterator() {
801813

802814
@Override
803815
public boolean contains(Object o) {
804-
if (o != null && o instanceof Map.Entry<?, ?>) {
805-
Map.Entry<?, ?> entry = (java.util.Map.Entry<?, ?>) o;
806-
Reference<K, V> reference = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
807-
Entry<K, V> other = (reference != null ? reference.get() : null);
808-
if (other != null) {
809-
return ObjectUtils.nullSafeEquals(entry.getValue(), other.getValue());
816+
if (o instanceof Map.Entry<?, ?>) {
817+
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
818+
Reference<K, V> ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
819+
Entry<K, V> otherEntry = (ref != null ? ref.get() : null);
820+
if (otherEntry != null) {
821+
return ObjectUtils.nullSafeEquals(otherEntry.getValue(), otherEntry.getValue());
810822
}
811823
}
812824
return false;

0 commit comments

Comments
 (0)