# HG changeset patch
# User psandoz
# Date 1431420640 -7200
# Node ID 415a5242e04681aeecbf134d09ff110ab8166452
# Parent 376bb53438b79f882a6dcbb517761b31ff05dd1d
8078645: removeIf(filter) in ConcurrentHashMap removes entries for which filter is false
Reviewed-by: martin, dholmes
Contributed-by: Doug Lea
, Paul Sandoz
diff -r 376bb53438b7 -r 415a5242e046 jdk/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java
--- a/jdk/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java Mon May 11 17:54:03 2015 -0700
+++ b/jdk/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java Tue May 12 10:50:40 2015 +0200
@@ -64,6 +64,7 @@
import java.util.function.Function;
import java.util.function.IntBinaryOperator;
import java.util.function.LongBinaryOperator;
+import java.util.function.Predicate;
import java.util.function.ToDoubleBiFunction;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntBiFunction;
@@ -1619,6 +1620,45 @@
}
/**
+ * Helper method for EntrySet.removeIf
+ */
+ boolean removeEntryIf(Predicate super Entry> function) {
+ if (function == null) throw new NullPointerException();
+ Node[] t;
+ boolean removed = false;
+ if ((t = table) != null) {
+ Traverser it = new Traverser(t, t.length, 0, t.length);
+ for (Node p; (p = it.advance()) != null; ) {
+ K k = p.key;
+ V v = p.val;
+ Map.Entry e = new AbstractMap.SimpleImmutableEntry<>(k, v);
+ if (function.test(e) && replaceNode(k, null, v) != null)
+ removed = true;
+ }
+ }
+ return removed;
+ }
+
+ /**
+ * Helper method for Values.removeIf
+ */
+ boolean removeValueIf(Predicate super V> function) {
+ if (function == null) throw new NullPointerException();
+ Node[] t;
+ boolean removed = false;
+ if ((t = table) != null) {
+ Traverser it = new Traverser(t, t.length, 0, t.length);
+ for (Node p; (p = it.advance()) != null; ) {
+ K k = p.key;
+ V v = p.val;
+ if (function.test(v) && replaceNode(k, null, v) != null)
+ removed = true;
+ }
+ }
+ return removed;
+ }
+
+ /**
* If the specified key is not already associated with a value,
* attempts to compute its value using the given mapping function
* and enters it into this map unless {@code null}. The entire
@@ -4690,6 +4730,10 @@
throw new UnsupportedOperationException();
}
+ public boolean removeIf(Predicate super V> filter) {
+ return map.removeValueIf(filter);
+ }
+
public Spliterator spliterator() {
Node[] t;
ConcurrentHashMap m = map;
@@ -4759,6 +4803,10 @@
return added;
}
+ public boolean removeIf(Predicate super Entry> filter) {
+ return map.removeEntryIf(filter);
+ }
+
public final int hashCode() {
int h = 0;
Node[] t;
diff -r 376bb53438b7 -r 415a5242e046 jdk/src/java.base/share/classes/java/util/concurrent/ConcurrentMap.java
--- a/jdk/src/java.base/share/classes/java/util/concurrent/ConcurrentMap.java Mon May 11 17:54:03 2015 -0700
+++ b/jdk/src/java.base/share/classes/java/util/concurrent/ConcurrentMap.java Tue May 12 10:50:40 2015 +0200
@@ -34,6 +34,7 @@
*/
package java.util.concurrent;
+
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
@@ -44,6 +45,14 @@
* A {@link java.util.Map} providing thread safety and atomicity
* guarantees.
*
+ *
To maintain the specified guarantees, default implementations of
+ * methods including {@link #putIfAbsent} inherited from {@link Map}
+ * must be overridden by implementations of this interface. Similarly,
+ * implementations of the collections returned by methods {@link
+ * #keySet}, {@link #values}, and {@link #entrySet} must override
+ * methods such as {@code removeIf} when necessary to
+ * preserve atomicity guarantees.
+ *
*
Memory consistency effects: As with other concurrent
* collections, actions in a thread prior to placing an object into a
* {@code ConcurrentMap} as a key or value
@@ -60,7 +69,7 @@
* @param the type of keys maintained by this map
* @param the type of mapped values
*/
-public interface ConcurrentMap extends Map {
+public interface ConcurrentMap extends Map {
/**
* {@inheritDoc}
@@ -86,9 +95,9 @@
* @implSpec The default implementation is equivalent to, for this
* {@code map}:
*
+ * for (Map.Entry entry : map.entrySet()) {
+ * action.accept(entry.getKey(), entry.getValue());
+ * }}
*
* @implNote The default implementation assumes that
* {@code IllegalStateException} thrown by {@code getKey()} or
@@ -101,13 +110,13 @@
@Override
default void forEach(BiConsumer super K, ? super V> action) {
Objects.requireNonNull(action);
- for (Map.Entry entry : entrySet()) {
+ for (Map.Entry entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
- } catch(IllegalStateException ise) {
+ } catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map.
continue;
}
@@ -117,14 +126,13 @@
/**
* If the specified key is not already associated
- * with a value, associate it with the given value.
- * This is equivalent to
- *
{@code
+ * with a value, associates it with the given value.
+ * This is equivalent to, for this {@code map}:
+ *
*
* except that the action is performed atomically.
*
@@ -147,18 +155,19 @@
* @throws IllegalArgumentException if some property of the specified key
* or value prevents it from being stored in this map
*/
- V putIfAbsent(K key, V value);
+ V putIfAbsent(K key, V value);
/**
* Removes the entry for a key only if currently mapped to a given value.
- * This is equivalent to
- *
{@code
- * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
+ * This is equivalent to, for this {@code map}:
+ *
*
* except that the action is performed atomically.
*
@@ -181,14 +190,15 @@
/**
* Replaces the entry for a key only if currently mapped to a given value.
- * This is equivalent to
- *
{@code
- * if (map.containsKey(key) && Objects.equals(map.get(key), oldValue)) {
+ * This is equivalent to, for this {@code map}:
+ *
*
* except that the action is performed atomically.
*
@@ -212,13 +222,12 @@
/**
* Replaces the entry for a key only if currently mapped to some value.
- * This is equivalent to
- *
{@code
- * if (map.containsKey(key)) {
+ * This is equivalent to, for this {@code map}:
+ *
*
* except that the action is performed atomically.
*
@@ -249,12 +258,14 @@
* @implSpec
*
The default implementation is equivalent to, for this {@code map}:
*
{@code
- * for ((Map.Entry entry : map.entrySet())
- * do {
- * K k = entry.getKey();
- * V v = entry.getValue();
- * } while(!replace(k, v, function.apply(k, v)));
- * }
+ * for (Map.Entry entry : map.entrySet()) {
+ * K k;
+ * V v;
+ * do {
+ * k = entry.getKey();
+ * v = entry.getValue();
+ * } while (!map.replace(k, v, function.apply(k, v)));
+ * }}
*
* The default implementation may retry these steps when multiple
* threads attempt updates including potentially calling the function
@@ -275,7 +286,7 @@
default void replaceAll(BiFunction super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
forEach((k,v) -> {
- while(!replace(k, v, function.apply(k, v))) {
+ while (!replace(k, v, function.apply(k, v))) {
// v changed or k is gone
if ( (v = get(k)) == null) {
// k is no longer in the map.
@@ -295,11 +306,10 @@
*
*
{@code
* if (map.get(key) == null) {
- * V newValue = mappingFunction.apply(key);
- * if (newValue != null)
- * return map.putIfAbsent(key, newValue);
- * }
- * }
+ * V newValue = mappingFunction.apply(key);
+ * if (newValue != null)
+ * return map.putIfAbsent(key, newValue);
+ * }}
*
* The default implementation may retry these steps when multiple
* threads attempt updates including potentially calling the mapping
@@ -331,18 +341,17 @@
* @implSpec
* The default implementation is equivalent to performing the following
* steps for this {@code map}, then returning the current value or
- * {@code null} if now absent. :
+ * {@code null} if now absent:
*
*