)f).first : null);
+ while (p != null) {
+ --delta;
+ p = p.next;
+ }
+ setTabAt(tab, i++, null);
+ }
+ }
+ }
+ }
+ if (delta != 0L)
+ addCount(delta, -1);
+ }
+
+ /**
+ * Returns a {@link Set} view of the keys contained in this map.
+ * The set is backed by the map, so changes to the map are
+ * reflected in the set, and vice-versa. The set supports element
+ * removal, which removes the corresponding mapping from this map,
+ * via the {@code Iterator.remove}, {@code Set.remove},
+ * {@code removeAll}, {@code retainAll}, and {@code clear}
+ * operations. It does not support the {@code add} or
+ * {@code addAll} operations.
+ *
+ * The view's {@code iterator} is a "weakly consistent" iterator
+ * that will never throw {@link ConcurrentModificationException},
+ * and guarantees to traverse elements as they existed upon
+ * construction of the iterator, and may (but is not guaranteed to)
+ * reflect any modifications subsequent to construction.
+ *
+ * @return the set view
+ */
+ public KeySetView keySet() {
+ KeySetView ks;
+ return (ks = keySet) != null ? ks : (keySet = new KeySetView(this, null));
+ }
+
+ /**
+ * Returns a {@link Collection} view of the values contained in this map.
+ * The collection is backed by the map, so changes to the map are
+ * reflected in the collection, and vice-versa. The collection
+ * supports element removal, which removes the corresponding
+ * mapping from this map, via the {@code Iterator.remove},
+ * {@code Collection.remove}, {@code removeAll},
+ * {@code retainAll}, and {@code clear} operations. It does not
+ * support the {@code add} or {@code addAll} operations.
+ *
+ * The view's {@code iterator} is a "weakly consistent" iterator
+ * that will never throw {@link ConcurrentModificationException},
+ * and guarantees to traverse elements as they existed upon
+ * construction of the iterator, and may (but is not guaranteed to)
+ * reflect any modifications subsequent to construction.
+ *
+ * @return the collection view
+ */
+ public Collection values() {
+ ValuesView vs;
+ return (vs = values) != null ? vs : (values = new ValuesView(this));
+ }
+
+ /**
+ * Returns a {@link Set} view of the mappings contained in this map.
+ * The set is backed by the map, so changes to the map are
+ * reflected in the set, and vice-versa. The set supports element
+ * removal, which removes the corresponding mapping from the map,
+ * via the {@code Iterator.remove}, {@code Set.remove},
+ * {@code removeAll}, {@code retainAll}, and {@code clear}
+ * operations.
+ *
+ * The view's {@code iterator} is a "weakly consistent" iterator
+ * that will never throw {@link ConcurrentModificationException},
+ * and guarantees to traverse elements as they existed upon
+ * construction of the iterator, and may (but is not guaranteed to)
+ * reflect any modifications subsequent to construction.
+ *
+ * @return the set view
+ */
+ public Set> entrySet() {
+ EntrySetView es;
+ return (es = entrySet) != null ? es : (entrySet = new EntrySetView(this));
+ }
+
+ /**
+ * Returns the hash code value for this {@link Map}, i.e.,
+ * the sum of, for each key-value pair in the map,
+ * {@code key.hashCode() ^ value.hashCode()}.
+ *
+ * @return the hash code value for this map
+ */
+ public int hashCode() {
+ int h = 0;
+ Node[] t;
+ if ((t = table) != null) {
+ Traverser it = new Traverser(t, t.length, 0, t.length);
+ for (Node p; (p = it.advance()) != null; )
+ h += p.key.hashCode() ^ p.val.hashCode();
+ }
+ return h;
+ }
+
+ /**
+ * Returns a string representation of this map. The string
+ * representation consists of a list of key-value mappings (in no
+ * particular order) enclosed in braces ("{@code {}}"). Adjacent
+ * mappings are separated by the characters {@code ", "} (comma
+ * and space). Each key-value mapping is rendered as the key
+ * followed by an equals sign ("{@code =}") followed by the
+ * associated value.
+ *
+ * @return a string representation of this map
+ */
+ public String toString() {
+ Node[] t;
+ int f = (t = table) == null ? 0 : t.length;
+ Traverser it = new Traverser(t, f, 0, f);
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ Node p;
+ if ((p = it.advance()) != null) {
+ for (;;) {
+ K k = p.key;
+ V v = p.val;
+ sb.append(k == this ? "(this Map)" : k);
+ sb.append('=');
+ sb.append(v == this ? "(this Map)" : v);
+ if ((p = it.advance()) == null)
+ break;
+ sb.append(',').append(' ');
+ }
+ }
+ return sb.append('}').toString();
+ }
+
+ /**
+ * Compares the specified object with this map for equality.
+ * Returns {@code true} if the given object is a map with the same
+ * mappings as this map. This operation may return misleading
+ * results if either map is concurrently modified during execution
+ * of this method.
+ *
+ * @param o object to be compared for equality with this map
+ * @return {@code true} if the specified object is equal to this map
+ */
+ public boolean equals(Object o) {
+ if (o != this) {
+ if (!(o instanceof Map))
+ return false;
+ Map,?> m = (Map,?>) o;
+ Node[] t;
+ int f = (t = table) == null ? 0 : t.length;
+ Traverser it = new Traverser(t, f, 0, f);
+ for (Node p; (p = it.advance()) != null; ) {
+ V val = p.val;
+ Object v = m.get(p.key);
+ if (v == null || (v != val && !v.equals(val)))
+ return false;
+ }
+ for (Map.Entry,?> e : m.entrySet()) {
+ Object mk, mv, v;
+ if ((mk = e.getKey()) == null ||
+ (mv = e.getValue()) == null ||
+ (v = get(mk)) == null ||
+ (mv != v && !mv.equals(v)))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Stripped-down version of helper class used in previous version,
+ * declared for the sake of serialization compatibility
+ */
+ static class Segment extends ReentrantLock implements Serializable {
+ private static final long serialVersionUID = 2249069246763182397L;
+ final float loadFactor;
+ Segment(float lf) { this.loadFactor = lf; }
+ }
+
+ /**
+ * Saves the state of the {@code ConcurrentHashMapV8} instance to a
+ * stream (i.e., serializes it).
+ * @param s the stream
+ * @throws java.io.IOException if an I/O error occurs
+ * @serialData
+ * the key (Object) and value (Object)
+ * for each key-value mapping, followed by a null pair.
+ * The key-value mappings are emitted in no particular order.
+ */
+ private void writeObject(java.io.ObjectOutputStream s)
+ throws java.io.IOException {
+ // For serialization compatibility
+ // Emulate segment calculation from previous version of this class
+ int sshift = 0;
+ int ssize = 1;
+ while (ssize < DEFAULT_CONCURRENCY_LEVEL) {
+ ++sshift;
+ ssize <<= 1;
+ }
+ int segmentShift = 32 - sshift;
+ int segmentMask = ssize - 1;
+ @SuppressWarnings("unchecked") Segment[] segments = (Segment[])
+ new Segment,?>[DEFAULT_CONCURRENCY_LEVEL];
+ for (int i = 0; i < segments.length; ++i)
+ segments[i] = new Segment(LOAD_FACTOR);
+ s.putFields().put("segments", segments);
+ s.putFields().put("segmentShift", segmentShift);
+ s.putFields().put("segmentMask", segmentMask);
+ s.writeFields();
+
+ Node[] t;
+ if ((t = table) != null) {
+ Traverser it = new Traverser(t, t.length, 0, t.length);
+ for (Node p; (p = it.advance()) != null; ) {
+ s.writeObject(p.key);
+ s.writeObject(p.val);
+ }
+ }
+ s.writeObject(null);
+ s.writeObject(null);
+ segments = null; // throw away
+ }
+
+ /**
+ * Reconstitutes the instance from a stream (that is, deserializes it).
+ * @param s the stream
+ * @throws ClassNotFoundException if the class of a serialized object
+ * could not be found
+ * @throws java.io.IOException if an I/O error occurs
+ */
+ private void readObject(java.io.ObjectInputStream s)
+ throws java.io.IOException, ClassNotFoundException {
+ /*
+ * To improve performance in typical cases, we create nodes
+ * while reading, then place in table once size is known.
+ * However, we must also validate uniqueness and deal with
+ * overpopulated bins while doing so, which requires
+ * specialized versions of putVal mechanics.
+ */
+ sizeCtl = -1; // force exclusion for table construction
+ s.defaultReadObject();
+ long size = 0L;
+ Node p = null;
+ for (;;) {
+ @SuppressWarnings("unchecked") K k = (K) s.readObject();
+ @SuppressWarnings("unchecked") V v = (V) s.readObject();
+ if (k != null && v != null) {
+ p = new Node(spread(k.hashCode()), k, v, p);
+ ++size;
+ }
+ else
+ break;
+ }
+ if (size == 0L)
+ sizeCtl = 0;
+ else {
+ int n;
+ if (size >= (long)(MAXIMUM_CAPACITY >>> 1))
+ n = MAXIMUM_CAPACITY;
+ else {
+ int sz = (int)size;
+ n = tableSizeFor(sz + (sz >>> 1) + 1);
+ }
+ @SuppressWarnings("unchecked")
+ Node[] tab = (Node[])new Node,?>[n];
+ int mask = n - 1;
+ long added = 0L;
+ while (p != null) {
+ boolean insertAtFront;
+ Node next = p.next, first;
+ int h = p.hash, j = h & mask;
+ if ((first = tabAt(tab, j)) == null)
+ insertAtFront = true;
+ else {
+ K k = p.key;
+ if (first.hash < 0) {
+ TreeBin t = (TreeBin)first;
+ if (t.putTreeVal(h, k, p.val) == null)
+ ++added;
+ insertAtFront = false;
+ }
+ else {
+ int binCount = 0;
+ insertAtFront = true;
+ Node q; K qk;
+ for (q = first; q != null; q = q.next) {
+ if (q.hash == h &&
+ ((qk = q.key) == k ||
+ (qk != null && k.equals(qk)))) {
+ insertAtFront = false;
+ break;
+ }
+ ++binCount;
+ }
+ if (insertAtFront && binCount >= TREEIFY_THRESHOLD) {
+ insertAtFront = false;
+ ++added;
+ p.next = first;
+ TreeNode hd = null, tl = null;
+ for (q = p; q != null; q = q.next) {
+ TreeNode t = new TreeNode
+ (q.hash, q.key, q.val, null, null);
+ if ((t.prev = tl) == null)
+ hd = t;
+ else
+ tl.next = t;
+ tl = t;
+ }
+ setTabAt(tab, j, new TreeBin(hd));
+ }
+ }
+ }
+ if (insertAtFront) {
+ ++added;
+ p.next = first;
+ setTabAt(tab, j, p);
+ }
+ p = next;
+ }
+ table = tab;
+ sizeCtl = n - (n >>> 2);
+ baseCount = added;
+ }
+ }
+
+ // ConcurrentMap methods
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return the previous value associated with the specified key,
+ * or {@code null} if there was no mapping for the key
+ * @throws NullPointerException if the specified key or value is null
+ */
+ public V putIfAbsent(K key, V value) {
+ return putVal(key, value, true);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws NullPointerException if the specified key is null
+ */
+ public boolean remove(Object key, Object value) {
+ if (key == null)
throw new NullPointerException();
- return (V)internalPut(key, value);
+ return value != null && replaceNode(key, null, value) != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws NullPointerException if any of the arguments are null
+ */
+ public boolean replace(K key, V oldValue, V newValue) {
+ if (key == null || oldValue == null || newValue == null)
+ throw new NullPointerException();
+ return replaceNode(key, newValue, oldValue) != null;
}
/**
@@ -2794,49 +1569,69 @@ public class ConcurrentHashMapV8
* or {@code null} if there was no mapping for the key
* @throws NullPointerException if the specified key or value is null
*/
- @SuppressWarnings("unchecked") public V putIfAbsent(K key, V value) {
+ public V replace(K key, V value) {
if (key == null || value == null)
throw new NullPointerException();
- return (V)internalPutIfAbsent(key, value);
+ return replaceNode(key, value, null);
}
+ // Overrides of JDK8+ Map extension method defaults
+
/**
- * Copies all of the mappings from the specified map to this one.
- * These mappings replace any mappings that this map had for any of the
- * keys currently in the specified map.
+ * Returns the value to which the specified key is mapped, or the
+ * given default value if this map contains no mapping for the
+ * key.
*
- * @param m mappings to be stored in this map
+ * @param key the key whose associated value is to be returned
+ * @param defaultValue the value to return if this map contains
+ * no mapping for the given key
+ * @return the mapping for the key, if present; else the default value
+ * @throws NullPointerException if the specified key is null
*/
- public void putAll(Map extends K, ? extends V> m) {
- internalPutAll(m);
+ public V getOrDefault(Object key, V defaultValue) {
+ V v;
+ return (v = get(key)) == null ? defaultValue : v;
+ }
+
+ public void forEach(BiAction super K, ? super V> action) {
+ if (action == null) throw new NullPointerException();
+ Node[] t;
+ if ((t = table) != null) {
+ Traverser it = new Traverser(t, t.length, 0, t.length);
+ for (Node p; (p = it.advance()) != null; ) {
+ action.apply(p.key, p.val);
+ }
+ }
+ }
+
+ public void replaceAll(BiFun super K, ? super V, ? extends V> function) {
+ if (function == null) throw new NullPointerException();
+ Node[] t;
+ if ((t = table) != null) {
+ Traverser it = new Traverser(t, t.length, 0, t.length);
+ for (Node p; (p = it.advance()) != null; ) {
+ V oldValue = p.val;
+ for (K key = p.key;;) {
+ V newValue = function.apply(key, oldValue);
+ if (newValue == null)
+ throw new NullPointerException();
+ if (replaceNode(key, newValue, oldValue) != null ||
+ (oldValue = get(key)) == null)
+ break;
+ }
+ }
+ }
}
/**
* If the specified key is not already associated with a value,
- * computes its value using the given mappingFunction and enters
- * it into the map unless null. This is equivalent to
- * {@code
- * if (map.containsKey(key))
- * return map.get(key);
- * value = mappingFunction.apply(key);
- * if (value != null)
- * map.put(key, value);
- * return value;}
- *
- * except that the action is performed atomically. If the
- * function returns {@code null} no mapping is recorded. If the
- * function itself throws an (unchecked) exception, the exception
- * is rethrown to its caller, and no mapping is recorded. Some
- * attempted update operations on this map by other threads may be
- * blocked while computation is in progress, so the computation
- * should be short and simple, and must not attempt to update any
- * other mappings of this Map. The most appropriate usage is to
- * construct a new object serving as an initial mapped value, or
- * memoized result, as in:
- *
- * {@code
- * map.computeIfAbsent(key, new Fun() {
- * public V map(K k) { return new Value(f(k)); }});}
+ * attempts to compute its value using the given mapping function
+ * and enters it into this map unless {@code null}. The entire
+ * method invocation is performed atomically, so the function is
+ * applied at most once per key. Some attempted update operations
+ * on this map by other threads may be blocked while computation
+ * is in progress, so the computation should be short and simple,
+ * and must not attempt to update any other mappings of this map.
*
* @param key key with which the specified value is to be associated
* @param mappingFunction the function to compute a value
@@ -2850,37 +1645,97 @@ public class ConcurrentHashMapV8
* @throws RuntimeException or Error if the mappingFunction does so,
* in which case the mapping is left unestablished
*/
- @SuppressWarnings("unchecked") public V computeIfAbsent
- (K key, Fun super K, ? extends V> mappingFunction) {
+ public V computeIfAbsent(K key, Fun super K, ? extends V> mappingFunction) {
if (key == null || mappingFunction == null)
throw new NullPointerException();
- return (V)internalComputeIfAbsent(key, mappingFunction);
+ int h = spread(key.hashCode());
+ V val = null;
+ int binCount = 0;
+ for (Node[] tab = table;;) {
+ Node f; int n, i, fh;
+ if (tab == null || (n = tab.length) == 0)
+ tab = initTable();
+ else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
+ Node r = new ReservationNode();
+ synchronized (r) {
+ if (casTabAt(tab, i, null, r)) {
+ binCount = 1;
+ Node node = null;
+ try {
+ if ((val = mappingFunction.apply(key)) != null)
+ node = new Node(h, key, val, null);
+ } finally {
+ setTabAt(tab, i, node);
+ }
+ }
+ }
+ if (binCount != 0)
+ break;
+ }
+ else if ((fh = f.hash) == MOVED)
+ tab = helpTransfer(tab, f);
+ else {
+ boolean added = false;
+ synchronized (f) {
+ if (tabAt(tab, i) == f) {
+ if (fh >= 0) {
+ binCount = 1;
+ for (Node e = f;; ++binCount) {
+ K ek; V ev;
+ if (e.hash == h &&
+ ((ek = e.key) == key ||
+ (ek != null && key.equals(ek)))) {
+ val = e.val;
+ break;
+ }
+ Node pred = e;
+ if ((e = e.next) == null) {
+ if ((val = mappingFunction.apply(key)) != null) {
+ added = true;
+ pred.next = new Node(h, key, val, null);
+ }
+ break;
+ }
+ }
+ }
+ else if (f instanceof TreeBin) {
+ binCount = 2;
+ TreeBin t = (TreeBin)f;
+ TreeNode r, p;
+ if ((r = t.root) != null &&
+ (p = r.findTreeNode(h, key, null)) != null)
+ val = p.val;
+ else if ((val = mappingFunction.apply(key)) != null) {
+ added = true;
+ t.putTreeVal(h, key, val);
+ }
+ }
+ }
+ }
+ if (binCount != 0) {
+ if (binCount >= TREEIFY_THRESHOLD)
+ treeifyBin(tab, i);
+ if (!added)
+ return val;
+ break;
+ }
+ }
+ }
+ if (val != null)
+ addCount(1L, binCount);
+ return val;
}
/**
- * If the given key is present, computes a new mapping value given a key and
- * its current mapped value. This is equivalent to
- * {@code
- * if (map.containsKey(key)) {
- * value = remappingFunction.apply(key, map.get(key));
- * if (value != null)
- * map.put(key, value);
- * else
- * map.remove(key);
- * }
- * }
- *
- * except that the action is performed atomically. If the
- * function returns {@code null}, the mapping is removed. If the
- * function itself throws an (unchecked) exception, the exception
- * is rethrown to its caller, and the current mapping is left
- * unchanged. Some attempted update operations on this map by
- * other threads may be blocked while computation is in progress,
- * so the computation should be short and simple, and must not
- * attempt to update any other mappings of this Map. For example,
- * to either create or append new messages to a value mapping:
+ * If the value for the specified key is present, attempts to
+ * compute a new mapping given the key and its current mapped
+ * value. The entire method invocation is performed atomically.
+ * Some attempted update operations on this map by other threads
+ * may be blocked while computation is in progress, so the
+ * computation should be short and simple, and must not attempt to
+ * update any other mappings of this map.
*
- * @param key key with which the specified value is to be associated
+ * @param key key with which a value may be associated
* @param remappingFunction the function to compute a value
* @return the new value associated with the specified key, or null if none
* @throws NullPointerException if the specified key or remappingFunction
@@ -2891,41 +1746,84 @@ public class ConcurrentHashMapV8
* @throws RuntimeException or Error if the remappingFunction does so,
* in which case the mapping is unchanged
*/
- @SuppressWarnings("unchecked") public V computeIfPresent
- (K key, BiFun super K, ? super V, ? extends V> remappingFunction) {
+ public V computeIfPresent(K key, BiFun super K, ? super V, ? extends V> remappingFunction) {
if (key == null || remappingFunction == null)
throw new NullPointerException();
- return (V)internalCompute(key, true, remappingFunction);
+ int h = spread(key.hashCode());
+ V val = null;
+ int delta = 0;
+ int binCount = 0;
+ for (Node[] tab = table;;) {
+ Node f; int n, i, fh;
+ if (tab == null || (n = tab.length) == 0)
+ tab = initTable();
+ else if ((f = tabAt(tab, i = (n - 1) & h)) == null)
+ break;
+ else if ((fh = f.hash) == MOVED)
+ tab = helpTransfer(tab, f);
+ else {
+ synchronized (f) {
+ if (tabAt(tab, i) == f) {
+ if (fh >= 0) {
+ binCount = 1;
+ for (Node e = f, pred = null;; ++binCount) {
+ K ek;
+ if (e.hash == h &&
+ ((ek = e.key) == key ||
+ (ek != null && key.equals(ek)))) {
+ val = remappingFunction.apply(key, e.val);
+ if (val != null)
+ e.val = val;
+ else {
+ delta = -1;
+ Node en = e.next;
+ if (pred != null)
+ pred.next = en;
+ else
+ setTabAt(tab, i, en);
+ }
+ break;
+ }
+ pred = e;
+ if ((e = e.next) == null)
+ break;
+ }
+ }
+ else if (f instanceof TreeBin) {
+ binCount = 2;
+ TreeBin t = (TreeBin)f;
+ TreeNode r, p;
+ if ((r = t.root) != null &&
+ (p = r.findTreeNode(h, key, null)) != null) {
+ val = remappingFunction.apply(key, p.val);
+ if (val != null)
+ p.val = val;
+ else {
+ delta = -1;
+ if (t.removeTreeNode(p))
+ setTabAt(tab, i, untreeify(t.first));
+ }
+ }
+ }
+ }
+ }
+ if (binCount != 0)
+ break;
+ }
+ }
+ if (delta != 0)
+ addCount((long)delta, binCount);
+ return val;
}
/**
- * Computes a new mapping value given a key and
- * its current mapped value (or {@code null} if there is no current
- * mapping). This is equivalent to
- * {@code
- * value = remappingFunction.apply(key, map.get(key));
- * if (value != null)
- * map.put(key, value);
- * else
- * map.remove(key);
- * }
- *
- * except that the action is performed atomically. If the
- * function returns {@code null}, the mapping is removed. If the
- * function itself throws an (unchecked) exception, the exception
- * is rethrown to its caller, and the current mapping is left
- * unchanged. Some attempted update operations on this map by
- * other threads may be blocked while computation is in progress,
- * so the computation should be short and simple, and must not
- * attempt to update any other mappings of this Map. For example,
- * to either create or append new messages to a value mapping:
- *
- * {@code
- * Map map = ...;
- * final String msg = ...;
- * map.compute(key, new BiFun() {
- * public String apply(Key k, String v) {
- * return (v == null) ? msg : v + msg;});}}
+ * Attempts to compute a mapping for the specified key and its
+ * current mapped value (or {@code null} if there is no current
+ * mapping). The entire method invocation is performed atomically.
+ * Some attempted update operations on this map by other threads
+ * may be blocked while computation is in progress, so the
+ * computation should be short and simple, and must not attempt to
+ * update any other mappings of this Map.
*
* @param key key with which the specified value is to be associated
* @param remappingFunction the function to compute a value
@@ -2938,125 +1836,319 @@ public class ConcurrentHashMapV8
* @throws RuntimeException or Error if the remappingFunction does so,
* in which case the mapping is unchanged
*/
- @SuppressWarnings("unchecked") public V compute
- (K key, BiFun super K, ? super V, ? extends V> remappingFunction) {
+ public V compute(K key,
+ BiFun super K, ? super V, ? extends V> remappingFunction) {
if (key == null || remappingFunction == null)
throw new NullPointerException();
- return (V)internalCompute(key, false, remappingFunction);
+ int h = spread(key.hashCode());
+ V val = null;
+ int delta = 0;
+ int binCount = 0;
+ for (Node[] tab = table;;) {
+ Node f; int n, i, fh;
+ if (tab == null || (n = tab.length) == 0)
+ tab = initTable();
+ else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
+ Node r = new ReservationNode();
+ synchronized (r) {
+ if (casTabAt(tab, i, null, r)) {
+ binCount = 1;
+ Node node = null;
+ try {
+ if ((val = remappingFunction.apply(key, null)) != null) {
+ delta = 1;
+ node = new Node(h, key, val, null);
+ }
+ } finally {
+ setTabAt(tab, i, node);
+ }
+ }
+ }
+ if (binCount != 0)
+ break;
+ }
+ else if ((fh = f.hash) == MOVED)
+ tab = helpTransfer(tab, f);
+ else {
+ synchronized (f) {
+ if (tabAt(tab, i) == f) {
+ if (fh >= 0) {
+ binCount = 1;
+ for (Node e = f, pred = null;; ++binCount) {
+ K ek;
+ if (e.hash == h &&
+ ((ek = e.key) == key ||
+ (ek != null && key.equals(ek)))) {
+ val = remappingFunction.apply(key, e.val);
+ if (val != null)
+ e.val = val;
+ else {
+ delta = -1;
+ Node en = e.next;
+ if (pred != null)
+ pred.next = en;
+ else
+ setTabAt(tab, i, en);
+ }
+ break;
+ }
+ pred = e;
+ if ((e = e.next) == null) {
+ val = remappingFunction.apply(key, null);
+ if (val != null) {
+ delta = 1;
+ pred.next =
+ new Node(h, key, val, null);
+ }
+ break;
+ }
+ }
+ }
+ else if (f instanceof TreeBin) {
+ binCount = 1;
+ TreeBin t = (TreeBin)f;
+ TreeNode r, p;
+ if ((r = t.root) != null)
+ p = r.findTreeNode(h, key, null);
+ else
+ p = null;
+ V pv = (p == null) ? null : p.val;
+ val = remappingFunction.apply(key, pv);
+ if (val != null) {
+ if (p != null)
+ p.val = val;
+ else {
+ delta = 1;
+ t.putTreeVal(h, key, val);
+ }
+ }
+ else if (p != null) {
+ delta = -1;
+ if (t.removeTreeNode(p))
+ setTabAt(tab, i, untreeify(t.first));
+ }
+ }
+ }
+ }
+ if (binCount != 0) {
+ if (binCount >= TREEIFY_THRESHOLD)
+ treeifyBin(tab, i);
+ break;
+ }
+ }
+ }
+ if (delta != 0)
+ addCount((long)delta, binCount);
+ return val;
}
/**
- * If the specified key is not already associated
- * with a value, associate it with the given value.
- * Otherwise, replace the value with the results of
- * the given remapping function. This is equivalent to:
- * {@code
- * if (!map.containsKey(key))
- * map.put(value);
- * else {
- * newValue = remappingFunction.apply(map.get(key), value);
- * if (value != null)
- * map.put(key, value);
- * else
- * map.remove(key);
- * }
- * }
- * except that the action is performed atomically. If the
- * function returns {@code null}, the mapping is removed. If the
- * function itself throws an (unchecked) exception, the exception
- * is rethrown to its caller, and the current mapping is left
- * unchanged. Some attempted update operations on this map by
- * other threads may be blocked while computation is in progress,
- * so the computation should be short and simple, and must not
- * attempt to update any other mappings of this Map.
+ * If the specified key is not already associated with a
+ * (non-null) value, associates it with the given value.
+ * Otherwise, replaces the value with the results of the given
+ * remapping function, or removes if {@code null}. The entire
+ * method invocation is performed atomically. Some attempted
+ * update operations on this map by other threads may be blocked
+ * while computation is in progress, so the computation should be
+ * short and simple, and must not attempt to update any other
+ * mappings of this Map.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value the value to use if absent
+ * @param remappingFunction the function to recompute a value if present
+ * @return the new value associated with the specified key, or null if none
+ * @throws NullPointerException if the specified key or the
+ * remappingFunction is null
+ * @throws RuntimeException or Error if the remappingFunction does so,
+ * in which case the mapping is unchanged
*/
- @SuppressWarnings("unchecked") public V merge
- (K key, V value, BiFun super V, ? super V, ? extends V> remappingFunction) {
+ public V merge(K key, V value, BiFun super V, ? super V, ? extends V> remappingFunction) {
if (key == null || value == null || remappingFunction == null)
throw new NullPointerException();
- return (V)internalMerge(key, value, remappingFunction);
+ int h = spread(key.hashCode());
+ V val = null;
+ int delta = 0;
+ int binCount = 0;
+ for (Node[] tab = table;;) {
+ Node f; int n, i, fh;
+ if (tab == null || (n = tab.length) == 0)
+ tab = initTable();
+ else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
+ if (casTabAt(tab, i, null, new Node(h, key, value, null))) {
+ delta = 1;
+ val = value;
+ break;
+ }
+ }
+ else if ((fh = f.hash) == MOVED)
+ tab = helpTransfer(tab, f);
+ else {
+ synchronized (f) {
+ if (tabAt(tab, i) == f) {
+ if (fh >= 0) {
+ binCount = 1;
+ for (Node e = f, pred = null;; ++binCount) {
+ K ek;
+ if (e.hash == h &&
+ ((ek = e.key) == key ||
+ (ek != null && key.equals(ek)))) {
+ val = remappingFunction.apply(e.val, value);
+ if (val != null)
+ e.val = val;
+ else {
+ delta = -1;
+ Node en = e.next;
+ if (pred != null)
+ pred.next = en;
+ else
+ setTabAt(tab, i, en);
+ }
+ break;
+ }
+ pred = e;
+ if ((e = e.next) == null) {
+ delta = 1;
+ val = value;
+ pred.next =
+ new Node(h, key, val, null);
+ break;
+ }
+ }
+ }
+ else if (f instanceof TreeBin) {
+ binCount = 2;
+ TreeBin t = (TreeBin)f;
+ TreeNode r = t.root;
+ TreeNode p = (r == null) ? null :
+ r.findTreeNode(h, key, null);
+ val = (p == null) ? value :
+ remappingFunction.apply(p.val, value);
+ if (val != null) {
+ if (p != null)
+ p.val = val;
+ else {
+ delta = 1;
+ t.putTreeVal(h, key, val);
+ }
+ }
+ else if (p != null) {
+ delta = -1;
+ if (t.removeTreeNode(p))
+ setTabAt(tab, i, untreeify(t.first));
+ }
+ }
+ }
+ }
+ if (binCount != 0) {
+ if (binCount >= TREEIFY_THRESHOLD)
+ treeifyBin(tab, i);
+ break;
+ }
+ }
+ }
+ if (delta != 0)
+ addCount((long)delta, binCount);
+ return val;
}
+ // Hashtable legacy methods
+
/**
- * Removes the key (and its corresponding value) from this map.
- * This method does nothing if the key is not in the map.
+ * Legacy method testing if some key maps into the specified value
+ * in this table. This method is identical in functionality to
+ * {@link #containsValue(Object)}, and exists solely to ensure
+ * full compatibility with class {@link java.util.Hashtable},
+ * which supported this method prior to introduction of the
+ * Java Collections framework.
*
- * @param key the key that needs to be removed
- * @return the previous value associated with {@code key}, or
- * {@code null} if there was no mapping for {@code key}
- * @throws NullPointerException if the specified key is null
+ * @param value a value to search for
+ * @return {@code true} if and only if some key maps to the
+ * {@code value} argument in this table as
+ * determined by the {@code equals} method;
+ * {@code false} otherwise
+ * @throws NullPointerException if the specified value is null
*/
- @SuppressWarnings("unchecked") public V remove(Object key) {
- if (key == null)
- throw new NullPointerException();
- return (V)internalReplace(key, null, null);
+ @Deprecated public boolean contains(Object value) {
+ return containsValue(value);
}
/**
- * {@inheritDoc}
+ * Returns an enumeration of the keys in this table.
*
- * @throws NullPointerException if the specified key is null
+ * @return an enumeration of the keys in this table
+ * @see #keySet()
*/
- public boolean remove(Object key, Object value) {
- if (key == null)
- throw new NullPointerException();
- if (value == null)
- return false;
- return internalReplace(key, null, value) != null;
+ public Enumeration keys() {
+ Node[] t;
+ int f = (t = table) == null ? 0 : t.length;
+ return new KeyIterator(t, f, 0, f, this);
}
/**
- * {@inheritDoc}
+ * Returns an enumeration of the values in this table.
*
- * @throws NullPointerException if any of the arguments are null
+ * @return an enumeration of the values in this table
+ * @see #values()
*/
- public boolean replace(K key, V oldValue, V newValue) {
- if (key == null || oldValue == null || newValue == null)
- throw new NullPointerException();
- return internalReplace(key, newValue, oldValue) != null;
+ public Enumeration elements() {
+ Node[] t;
+ int f = (t = table) == null ? 0 : t.length;
+ return new ValueIterator(t, f, 0, f, this);
}
+ // ConcurrentHashMapV8-only methods
+
/**
- * {@inheritDoc}
+ * Returns the number of mappings. This method should be used
+ * instead of {@link #size} because a ConcurrentHashMapV8 may
+ * contain more mappings than can be represented as an int. The
+ * value returned is an estimate; the actual count may differ if
+ * there are concurrent insertions or removals.
*
- * @return the previous value associated with the specified key,
- * or {@code null} if there was no mapping for the key
- * @throws NullPointerException if the specified key or value is null
+ * @return the number of mappings
+ * @since 1.8
*/
- @SuppressWarnings("unchecked") public V replace(K key, V value) {
- if (key == null || value == null)
- throw new NullPointerException();
- return (V)internalReplace(key, value, null);
+ public long mappingCount() {
+ long n = sumCount();
+ return (n < 0L) ? 0L : n; // ignore transient negative values
}
/**
- * Removes all of the mappings from this map.
+ * Creates a new {@link Set} backed by a ConcurrentHashMapV8
+ * from the given type to {@code Boolean.TRUE}.
+ *
+ * @return the new set
+ * @since 1.8
*/
- public void clear() {
- internalClear();
+ public static KeySetView newKeySet() {
+ return new KeySetView
+ (new ConcurrentHashMapV8(), Boolean.TRUE);
}
/**
- * Returns a {@link Set} view of the keys contained in this map.
- * The set is backed by the map, so changes to the map are
- * reflected in the set, and vice-versa.
+ * Creates a new {@link Set} backed by a ConcurrentHashMapV8
+ * from the given type to {@code Boolean.TRUE}.
*
- * @return the set view
+ * @param initialCapacity The implementation performs internal
+ * sizing to accommodate this many elements.
+ * @return the new set
+ * @throws IllegalArgumentException if the initial capacity of
+ * elements is negative
+ * @since 1.8
*/
- public KeySetView keySet() {
- KeySetView ks = keySet;
- return (ks != null) ? ks : (keySet = new KeySetView(this, null));
+ public static KeySetView newKeySet(int initialCapacity) {
+ return new KeySetView
+ (new ConcurrentHashMapV8(initialCapacity), Boolean.TRUE);
}
/**
* Returns a {@link Set} view of the keys in this map, using the
* given common mapped value for any additions (i.e., {@link
- * Collection#add} and {@link Collection#addAll}). This is of
- * course only appropriate if it is acceptable to use the same
- * value for all additions from this view.
+ * Collection#add} and {@link Collection#addAll(Collection)}).
+ * This is of course only appropriate if it is acceptable to use
+ * the same value for all additions from this view.
*
- * @param mappedValue the mapped value to use for any
- * additions.
+ * @param mappedValue the mapped value to use for any additions
* @return the set view
* @throws NullPointerException if the mappedValue is null
*/
@@ -3066,252 +2158,1230 @@ public class ConcurrentHashMapV8
return new KeySetView(this, mappedValue);
}
+ /* ---------------- Special Nodes -------------- */
+
/**
- * Returns a {@link Collection} view of the values contained in this map.
- * The collection is backed by the map, so changes to the map are
- * reflected in the collection, and vice-versa.
+ * A node inserted at head of bins during transfer operations.
*/
- public ValuesView values() {
- ValuesView vs = values;
- return (vs != null) ? vs : (values = new ValuesView(this));
+ static final class ForwardingNode extends Node {
+ final Node[] nextTable;
+ ForwardingNode(Node[] tab) {
+ super(MOVED, null, null, null);
+ this.nextTable = tab;
+ }
+
+ Node find(int h, Object k) {
+ // loop to avoid arbitrarily deep recursion on forwarding nodes
+ outer: for (Node[] tab = nextTable;;) {
+ Node e; int n;
+ if (k == null || tab == null || (n = tab.length) == 0 ||
+ (e = tabAt(tab, (n - 1) & h)) == null)
+ return null;
+ for (;;) {
+ int eh; K ek;
+ if ((eh = e.hash) == h &&
+ ((ek = e.key) == k || (ek != null && k.equals(ek))))
+ return e;
+ if (eh < 0) {
+ if (e instanceof ForwardingNode) {
+ tab = ((ForwardingNode)e).nextTable;
+ continue outer;
+ }
+ else
+ return e.find(h, k);
+ }
+ if ((e = e.next) == null)
+ return null;
+ }
+ }
+ }
}
/**
- * Returns a {@link Set} view of the mappings contained in this map.
- * The set is backed by the map, so changes to the map are
- * reflected in the set, and vice-versa. The set supports element
- * removal, which removes the corresponding mapping from the map,
- * via the {@code Iterator.remove}, {@code Set.remove},
- * {@code removeAll}, {@code retainAll}, and {@code clear}
- * operations. It does not support the {@code add} or
- * {@code addAll} operations.
- *
- * The view's {@code iterator} is a "weakly consistent" iterator
- * that will never throw {@link ConcurrentModificationException},
- * and guarantees to traverse elements as they existed upon
- * construction of the iterator, and may (but is not guaranteed to)
- * reflect any modifications subsequent to construction.
+ * A place-holder node used in computeIfAbsent and compute
*/
- public Set> entrySet() {
- EntrySetView es = entrySet;
- return (es != null) ? es : (entrySet = new EntrySetView(this));
+ static final class ReservationNode extends Node {
+ ReservationNode() {
+ super(RESERVED, null, null, null);
+ }
+
+ Node find(int h, Object k) {
+ return null;
+ }
}
+ /* ---------------- Table Initialization and Resizing -------------- */
+
/**
- * Returns an enumeration of the keys in this table.
- *
- * @return an enumeration of the keys in this table
- * @see #keySet()
+ * Returns the stamp bits for resizing a table of size n.
+ * Must be negative when shifted left by RESIZE_STAMP_SHIFT.
*/
- public Enumeration keys() {
- return new KeyIterator(this);
+ static final int resizeStamp(int n) {
+ return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
/**
- * Returns an enumeration of the values in this table.
- *
- * @return an enumeration of the values in this table
- * @see #values()
+ * Initializes table, using the size recorded in sizeCtl.
*/
- public Enumeration elements() {
- return new ValueIterator(this);
+ private final Node[] initTable() {
+ Node[] tab; int sc;
+ while ((tab = table) == null || tab.length == 0) {
+ if ((sc = sizeCtl) < 0)
+ Thread.yield(); // lost initialization race; just spin
+ else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
+ try {
+ if ((tab = table) == null || tab.length == 0) {
+ int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
+ @SuppressWarnings("unchecked")
+ Node[] nt = (Node[])new Node,?>[n];
+ table = tab = nt;
+ sc = n - (n >>> 2);
+ }
+ } finally {
+ sizeCtl = sc;
+ }
+ break;
+ }
+ }
+ return tab;
}
/**
- * Returns a partitionable iterator of the keys in this map.
- *
- * @return a partitionable iterator of the keys in this map
+ * Adds to count, and if table is too small and not already
+ * resizing, initiates transfer. If already resizing, helps
+ * perform transfer if work is available. Rechecks occupancy
+ * after a transfer to see if another resize is already needed
+ * because resizings are lagging additions.
+ *
+ * @param x the count to add
+ * @param check if <0, don't check resize, if <= 1 only check if uncontended
+ */
+ private final void addCount(long x, int check) {
+ CounterCell[] as; long b, s;
+ if ((as = counterCells) != null ||
+ !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
+ CounterHashCode hc; CounterCell a; long v; int m;
+ boolean uncontended = true;
+ if ((hc = threadCounterHashCode.get()) == null ||
+ as == null || (m = as.length - 1) < 0 ||
+ (a = as[m & hc.code]) == null ||
+ !(uncontended =
+ U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
+ fullAddCount(x, hc, uncontended);
+ return;
+ }
+ if (check <= 1)
+ return;
+ s = sumCount();
+ }
+ if (check >= 0) {
+ Node[] tab, nt; int n, sc;
+ while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
+ (n = tab.length) < MAXIMUM_CAPACITY) {
+ int rs = resizeStamp(n);
+ if (sc < 0) {
+ if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
+ sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
+ transferIndex <= 0)
+ break;
+ if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
+ transfer(tab, nt);
+ }
+ else if (U.compareAndSwapInt(this, SIZECTL, sc,
+ (rs << RESIZE_STAMP_SHIFT) + 2))
+ transfer(tab, null);
+ s = sumCount();
+ }
+ }
+ }
+
+ /**
+ * Helps transfer if a resize is in progress.
*/
- public Spliterator keySpliterator() {
- return new KeyIterator(this);
+ final Node[] helpTransfer(Node[] tab, Node f) {
+ Node[] nextTab; int sc;
+ if (tab != null && (f instanceof ForwardingNode) &&
+ (nextTab = ((ForwardingNode)f).nextTable) != null) {
+ int rs = resizeStamp(tab.length);
+ while (nextTab == nextTable && table == tab &&
+ (sc = sizeCtl) < 0) {
+ if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
+ sc == rs + MAX_RESIZERS || transferIndex <= 0)
+ break;
+ if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
+ transfer(tab, nextTab);
+ break;
+ }
+ }
+ return nextTab;
+ }
+ return table;
}
/**
- * Returns a partitionable iterator of the values in this map.
+ * Tries to presize table to accommodate the given number of elements.
*
- * @return a partitionable iterator of the values in this map
+ * @param size number of elements (doesn't need to be perfectly accurate)
*/
- public Spliterator valueSpliterator() {
- return new ValueIterator(this);
+ private final void tryPresize(int size) {
+ int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
+ tableSizeFor(size + (size >>> 1) + 1);
+ int sc;
+ while ((sc = sizeCtl) >= 0) {
+ Node