Skip to content

Commit 3fa56db

Browse files
kilinksbrannen
authored andcommitted
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. Closes gh-35817 Signed-off-by: Patrick Strawderman <pstrawderman@netflix.com> (cherry picked from commit ed75906)
1 parent ed444eb commit 3fa56db

File tree

2 files changed

+339
-15
lines changed

2 files changed

+339
-15
lines changed

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

Lines changed: 158 additions & 2 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;
@@ -104,6 +108,18 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
104108
@Nullable
105109
private volatile Set<Map.Entry<K, V>> entrySet;
106110

111+
/**
112+
* Late binding key set.
113+
*/
114+
@Nullable
115+
private Set<K> keySet;
116+
117+
/**
118+
* Late binding values collection.
119+
*/
120+
@Nullable
121+
private Collection<V> values;
122+
107123

108124
/**
109125
* Create a new {@code ConcurrentReferenceHashMap} instance.
@@ -528,6 +544,26 @@ public Set<Map.Entry<K, V>> entrySet() {
528544
return entrySet;
529545
}
530546

547+
@Override
548+
public Set<K> keySet() {
549+
Set<K> keySet = this.keySet;
550+
if (keySet == null) {
551+
keySet = new KeySet();
552+
this.keySet = keySet;
553+
}
554+
return keySet;
555+
}
556+
557+
@Override
558+
public Collection<V> values() {
559+
Collection<V> values = this.values;
560+
if (values == null) {
561+
values = new Values();
562+
this.values = values;
563+
}
564+
return values;
565+
}
566+
531567
@Nullable
532568
private <T> T doTask(@Nullable Object key, Task<T> task) {
533569
int hash = getHash(key);
@@ -969,7 +1005,7 @@ private interface Entries<V> {
9691005
/**
9701006
* Internal entry-set implementation.
9711007
*/
972-
private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
1008+
private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
9731009

9741010
@Override
9751011
public Iterator<Map.Entry<K, V>> iterator() {
@@ -1005,13 +1041,133 @@ public int size() {
10051041
public void clear() {
10061042
ConcurrentReferenceHashMap.this.clear();
10071043
}
1044+
1045+
@Override
1046+
public Spliterator<Map.Entry<K, V>> spliterator() {
1047+
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.CONCURRENT);
1048+
}
1049+
}
1050+
1051+
/**
1052+
* Internal key-set implementation.
1053+
*/
1054+
private final class KeySet extends AbstractSet<K> {
1055+
@Override
1056+
public Iterator<K> iterator() {
1057+
return new KeyIterator();
1058+
}
1059+
1060+
@Override
1061+
public int size() {
1062+
return ConcurrentReferenceHashMap.this.size();
1063+
}
1064+
1065+
@Override
1066+
public boolean isEmpty() {
1067+
return ConcurrentReferenceHashMap.this.isEmpty();
1068+
}
1069+
1070+
@Override
1071+
public void clear() {
1072+
ConcurrentReferenceHashMap.this.clear();
1073+
}
1074+
1075+
@Override
1076+
public boolean contains(Object k) {
1077+
return ConcurrentReferenceHashMap.this.containsKey(k);
1078+
}
1079+
1080+
@Override
1081+
public Spliterator<K> spliterator() {
1082+
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.CONCURRENT);
1083+
}
1084+
}
1085+
1086+
/**
1087+
* Internal key iterator implementation.
1088+
*/
1089+
private final class KeyIterator implements Iterator<K> {
1090+
1091+
private final Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
1092+
1093+
@Override
1094+
public boolean hasNext() {
1095+
return this.iterator.hasNext();
1096+
}
1097+
1098+
@Override
1099+
public void remove() {
1100+
this.iterator.remove();
1101+
}
1102+
1103+
@Override
1104+
public K next() {
1105+
return this.iterator.next().getKey();
1106+
}
1107+
}
1108+
1109+
/**
1110+
* Internal values collection implementation.
1111+
*/
1112+
private final class Values extends AbstractCollection<V> {
1113+
@Override
1114+
public Iterator<V> iterator() {
1115+
return new ValueIterator();
1116+
}
1117+
1118+
@Override
1119+
public int size() {
1120+
return ConcurrentReferenceHashMap.this.size();
1121+
}
1122+
1123+
@Override
1124+
public boolean isEmpty() {
1125+
return ConcurrentReferenceHashMap.this.isEmpty();
1126+
}
1127+
1128+
@Override
1129+
public void clear() {
1130+
ConcurrentReferenceHashMap.this.clear();
1131+
}
1132+
1133+
@Override
1134+
public boolean contains(Object v) {
1135+
return ConcurrentReferenceHashMap.this.containsValue(v);
1136+
}
1137+
1138+
@Override
1139+
public Spliterator<V> spliterator() {
1140+
return Spliterators.spliterator(this, Spliterator.CONCURRENT);
1141+
}
10081142
}
10091143

1144+
/**
1145+
* Internal value iterator implementation.
1146+
*/
1147+
private final class ValueIterator implements Iterator<V> {
1148+
1149+
private final Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
1150+
1151+
@Override
1152+
public boolean hasNext() {
1153+
return this.iterator.hasNext();
1154+
}
1155+
1156+
@Override
1157+
public void remove() {
1158+
this.iterator.remove();
1159+
}
1160+
1161+
@Override
1162+
public V next() {
1163+
return this.iterator.next().getValue();
1164+
}
1165+
}
10101166

10111167
/**
10121168
* Internal entry iterator implementation.
10131169
*/
1014-
private class EntryIterator implements Iterator<Map.Entry<K, V>> {
1170+
private final class EntryIterator implements Iterator<Map.Entry<K, V>> {
10151171

10161172
private int segmentIndex;
10171173

0 commit comments

Comments
 (0)