Skip to content

Commit aefaa72

Browse files
committed
Fix spliterator characteristics in ConcurrentReferenceHashMap
The Spliterators returned by values, entrySet, and keySet incorrectly reported the SIZED characteristic, instead of CONCURRENT. This could lead to bugs when the map is concurrently modified during a stream operation. For keySet and values, the incorrect characteristics are inherited from AbstractMap, so to rectify that the respective methods are overridden and custom collections are provided that report the correct Spliterator characteristics.
1 parent 18d8d45 commit aefaa72

File tree

2 files changed

+338
-16
lines changed

2 files changed

+338
-16
lines changed

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

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,19 @@
2020
import java.lang.ref.SoftReference;
2121
import java.lang.ref.WeakReference;
2222
import java.lang.reflect.Array;
23+
import java.util.AbstractCollection;
2324
import java.util.AbstractMap;
2425
import java.util.AbstractSet;
26+
import java.util.Collection;
2527
import java.util.Collections;
2628
import java.util.EnumSet;
2729
import java.util.HashSet;
2830
import java.util.Iterator;
2931
import java.util.Map;
3032
import java.util.NoSuchElementException;
3133
import java.util.Set;
34+
import java.util.Spliterator;
35+
import java.util.Spliterators;
3236
import java.util.concurrent.ConcurrentHashMap;
3337
import java.util.concurrent.ConcurrentMap;
3438
import java.util.concurrent.atomic.AtomicInteger;
@@ -101,7 +105,17 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
101105
/**
102106
* Late binding entry set.
103107
*/
104-
private volatile @Nullable Set<Map.Entry<K, V>> entrySet;
108+
private @Nullable Set<Map.Entry<K, V>> entrySet;
109+
110+
/**
111+
* Late binding key set.
112+
*/
113+
private @Nullable Set<K> keySet;
114+
115+
/**
116+
* Late binding values collection.
117+
*/
118+
private @Nullable Collection<V> values;
105119

106120

107121
/**
@@ -512,6 +526,26 @@ public Set<Map.Entry<K, V>> entrySet() {
512526
return entrySet;
513527
}
514528

529+
@Override
530+
public Set<K> keySet() {
531+
Set<K> keySet = this.keySet;
532+
if (keySet == null) {
533+
keySet = new KeySet();
534+
this.keySet = keySet;
535+
}
536+
return keySet;
537+
}
538+
539+
@Override
540+
public Collection<V> values() {
541+
Collection<V> values = this.values;
542+
if (values == null) {
543+
values = new Values();
544+
this.values = values;
545+
}
546+
return values;
547+
}
548+
515549
private <T> @Nullable T doTask(@Nullable Object key, Task<T> task) {
516550
int hash = getHash(key);
517551
return getSegmentForHash(hash).doTask(hash, key, task);
@@ -940,7 +974,7 @@ private interface Entries<V> {
940974
/**
941975
* Internal entry-set implementation.
942976
*/
943-
private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
977+
private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
944978

945979
@Override
946980
public Iterator<Map.Entry<K, V>> iterator() {
@@ -976,13 +1010,133 @@ public int size() {
9761010
public void clear() {
9771011
ConcurrentReferenceHashMap.this.clear();
9781012
}
1013+
1014+
@Override
1015+
public Spliterator<Map.Entry<K, V>> spliterator() {
1016+
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.CONCURRENT);
1017+
}
1018+
}
1019+
1020+
/**
1021+
* Internal key-set implementation.
1022+
*/
1023+
private final class KeySet extends AbstractSet<K> {
1024+
@Override
1025+
public Iterator<K> iterator() {
1026+
return new KeyIterator();
1027+
}
1028+
1029+
@Override
1030+
public int size() {
1031+
return ConcurrentReferenceHashMap.this.size();
1032+
}
1033+
1034+
@Override
1035+
public boolean isEmpty() {
1036+
return ConcurrentReferenceHashMap.this.isEmpty();
1037+
}
1038+
1039+
@Override
1040+
public void clear() {
1041+
ConcurrentReferenceHashMap.this.clear();
1042+
}
1043+
1044+
@Override
1045+
public boolean contains(Object k) {
1046+
return ConcurrentReferenceHashMap.this.containsKey(k);
1047+
}
1048+
1049+
@Override
1050+
public Spliterator<K> spliterator() {
1051+
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.CONCURRENT);
1052+
}
1053+
}
1054+
1055+
/**
1056+
* Internal key iterator implementation.
1057+
*/
1058+
private final class KeyIterator implements Iterator<K> {
1059+
1060+
private final Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
1061+
1062+
@Override
1063+
public boolean hasNext() {
1064+
return this.iterator.hasNext();
1065+
}
1066+
1067+
@Override
1068+
public void remove() {
1069+
this.iterator.remove();
1070+
}
1071+
1072+
@Override
1073+
public K next() {
1074+
return this.iterator.next().getKey();
1075+
}
1076+
}
1077+
1078+
/**
1079+
* Internal values collection implementation.
1080+
*/
1081+
private final class Values extends AbstractCollection<V> {
1082+
@Override
1083+
public Iterator<V> iterator() {
1084+
return new ValueIterator();
1085+
}
1086+
1087+
@Override
1088+
public int size() {
1089+
return ConcurrentReferenceHashMap.this.size();
1090+
}
1091+
1092+
@Override
1093+
public boolean isEmpty() {
1094+
return ConcurrentReferenceHashMap.this.isEmpty();
1095+
}
1096+
1097+
@Override
1098+
public void clear() {
1099+
ConcurrentReferenceHashMap.this.clear();
1100+
}
1101+
1102+
@Override
1103+
public boolean contains(Object v) {
1104+
return ConcurrentReferenceHashMap.this.containsValue(v);
1105+
}
1106+
1107+
@Override
1108+
public Spliterator<V> spliterator() {
1109+
return Spliterators.spliterator(this, Spliterator.CONCURRENT);
1110+
}
9791111
}
9801112

1113+
/**
1114+
* Internal value iterator implementation.
1115+
*/
1116+
private final class ValueIterator implements Iterator<V> {
1117+
1118+
private final Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
1119+
1120+
@Override
1121+
public boolean hasNext() {
1122+
return this.iterator.hasNext();
1123+
}
1124+
1125+
@Override
1126+
public void remove() {
1127+
this.iterator.remove();
1128+
}
1129+
1130+
@Override
1131+
public V next() {
1132+
return this.iterator.next().getValue();
1133+
}
1134+
}
9811135

9821136
/**
9831137
* Internal entry iterator implementation.
9841138
*/
985-
private class EntryIterator implements Iterator<Map.Entry<K, V>> {
1139+
private final class EntryIterator implements Iterator<Map.Entry<K, V>> {
9861140

9871141
private int segmentIndex;
9881142

0 commit comments

Comments
 (0)