ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/jsr166/jsr166/src/jsr166e/ConcurrentHashMapV8.java
(Generate patch)

Comparing jsr166/src/jsr166e/ConcurrentHashMapV8.java (file contents):
Revision 1.1 by dl, Sun Aug 28 19:08:07 2011 UTC vs.
Revision 1.9 by jsr166, Tue Aug 30 14:55:58 2011 UTC

# Line 54 | Line 54 | import java.io.Serializable;
54   * <p> Resizing this or any other kind of hash table is a relatively
55   * slow operation, so, when possible, it is a good idea to provide
56   * estimates of expected table sizes in constructors. Also, for
57 < * compatability with previous versions of this class, constructors
57 > * compatibility with previous versions of this class, constructors
58   * may optionally specify an expected {@code concurrencyLevel} as an
59   * additional hint for internal sizing.
60   *
# Line 83 | Line 83 | public class ConcurrentHashMapV8<K, V>
83  
84      /**
85       * A function computing a mapping from the given key to a value,
86 <     *  or <code>null</code> if there is no mapping. This is a
87 <     * place-holder for an upcoming JDK8 interface.
86 >     * or {@code null} if there is no mapping. This is a place-holder
87 >     * for an upcoming JDK8 interface.
88       */
89      public static interface MappingFunction<K, V> {
90          /**
# Line 125 | Line 125 | public class ConcurrentHashMapV8<K, V>
125       * within bins are always accurately traversable under volatile
126       * reads, so long as lookups check hash code and non-nullness of
127       * key and value before checking key equality. (All valid hash
128 <     * codes are nonnegative. Negative values are served for special
129 <     * nodes.)
128 >     * codes are nonnegative. Negative values are reserved for special
129 >     * forwarding nodes; see below.)
130       *
131       * A bin may be locked during update (insert, delete, and replace)
132       * operations.  We do not want to waste the space required to
# Line 146 | Line 146 | public class ConcurrentHashMapV8<K, V>
146       * there is no existing node during a put operation, then one can
147       * be CAS'ed in (without need for lock except in computeIfAbsent);
148       * the CAS serves as validation. This is on average the most
149 <     * common case for put operations. The expected number of locks
150 <     * covering different elements (i.e., bins with 2 or more nodes)
151 <     * is approximately 10% at steady state under default settings.
152 <     * Lock contention probability for two threads accessing arbitrary
153 <     * distinct elements is thus less than 1% even for small tables.
149 >     * common case for put operations -- under random hash codes, the
150 >     * distribution of nodes in bins follows a Poisson distribution
151 >     * (see http://en.wikipedia.org/wiki/Poisson_distribution) with a
152 >     * parameter of 0.5 on average under the default loadFactor of
153 >     * 0.75.  The expected number of locks covering different elements
154 >     * (i.e., bins with 2 or more nodes) is approximately 10% at
155 >     * steady state under default settings.  Lock contention
156 >     * probability for two threads accessing arbitrary distinct
157 >     * elements is, roughly, 1 / (8 * #elements).
158       *
159       * The table is resized when occupancy exceeds a threshold.  Only
160       * a single thread performs the resize (using field "resizing", to
# Line 172 | Line 176 | public class ConcurrentHashMapV8<K, V>
176       * complexity of access and iteration schemes that could admit
177       * out-of-order or concurrent bin transfers.
178       *
179 <     * (While not yet implemented, a similar traversal scheme can
180 <     * apply to partial traversals during partitioned aggregate
181 <     * operations. Also, read-only operations give up if ever
182 <     * forwarded to a null table, which provides support for
183 <     * shutdown-style clearing, which is also not currently
180 <     * implemented.)
179 >     * A similar traversal scheme (not yet implemented) can apply to
180 >     * partial traversals during partitioned aggregate operations.
181 >     * Also, read-only operations give up if ever forwarded to a null
182 >     * table, which provides support for shutdown-style clearing,
183 >     * which is also not currently implemented.
184       *
185       * The element count is maintained using a LongAdder, which avoids
186       * contention on updates but can encounter cache thrashing if read
187       * too frequently during concurrent updates. To avoid reading so
188 <     * often, resizing is attempted only upon adding to a bin already
189 <     * holding two or more nodes. Under the default threshold (0.75),
190 <     * and uniform hash distributions, the probability of this
188 >     * often, resizing is normally attempted only upon adding to a bin
189 >     * already holding two or more nodes. Under the default threshold
190 >     * (0.75), and uniform hash distributions, the probability of this
191       * occurring at threshold is around 13%, meaning that only about 1
192       * in 8 puts check threshold (and after resizing, many fewer do
193 <     * so). To increase the probablity that a resize occurs soon
194 <     * enough, we offset the threshold (see THRESHOLD_OFFSET) by the
195 <     * expected number of puts between checks. This is currently set
196 <     * to 8, in accord with the default load factor. In practice, this
197 <     * is rarely overridden, and in any case is close enough to other
198 <     * plausible values not to waste dynamic probablity computation
193 >     * so). But this approximation has high variance for small table
194 >     * sizes, so we check on any collision for sizes <= 64.  Further,
195 >     * to increase the probability that a resize occurs soon enough, we
196 >     * offset the threshold (see THRESHOLD_OFFSET) by the expected
197 >     * number of puts between checks. This is currently set to 8, in
198 >     * accord with the default load factor. In practice, this is
199 >     * rarely overridden, and in any case is close enough to other
200 >     * plausible values not to waste dynamic probability computation
201       * for more precision.
202       */
203  
# Line 212 | Line 217 | public class ConcurrentHashMapV8<K, V>
217  
218      /**
219       * The default initial table capacity.  Must be a power of 2, at
220 <     * least MINIMUM_CAPACITY and at most MAXIMUM_CAPACITY
220 >     * least MINIMUM_CAPACITY and at most MAXIMUM_CAPACITY.
221       */
222      static final int DEFAULT_CAPACITY = 16;
223  
# Line 229 | Line 234 | public class ConcurrentHashMapV8<K, V>
234      static final int DEFAULT_CONCURRENCY_LEVEL = 16;
235  
236      /**
237 <     * The count value to offset thesholds to compensate for checking
237 >     * The count value to offset thresholds to compensate for checking
238       * for resizing only when inserting into bins with two or more
239       * elements. See above for explanation.
240       */
# Line 266 | Line 271 | public class ConcurrentHashMapV8<K, V>
271      transient Set<Map.Entry<K,V>> entrySet;
272      transient Collection<V> values;
273  
274 <    /** For serialization compatability. Null unless serialized; see below */
274 >    /** For serialization compatibility. Null unless serialized; see below */
275      Segment<K,V>[] segments;
276  
277      /**
# Line 304 | Line 309 | public class ConcurrentHashMapV8<K, V>
309      }
310  
311      /*
312 <     * Volatile access nethods are used for table elements as well as
312 >     * Volatile access methods are used for table elements as well as
313       * elements of in-progress next table while resizing.  Uses in
314       * access and update methods are null checked by callers, and
315       * implicitly bounds-checked, relying on the invariants that tab
316       * arrays have non-zero size, and all indices are masked with
317       * (tab.length - 1) which is never negative and always less than
318 <     * length. The only other usage is in HashIterator.advance, which
319 <     * performs explicit checks.
318 >     * length. The "relaxed" non-volatile forms are used only during
319 >     * table initialization. The only other usage is in
320 >     * HashIterator.advance, which performs explicit checks.
321       */
322  
323      static final Node tabAt(Node[] tab, int i) { // used in HashIterator
# Line 326 | Line 332 | public class ConcurrentHashMapV8<K, V>
332          UNSAFE.putObjectVolatile(tab, ((long)i<<ASHIFT)+ABASE, v);
333      }
334  
335 +    private static final Node relaxedTabAt(Node[] tab, int i) {
336 +        return (Node)UNSAFE.getObject(tab, ((long)i<<ASHIFT)+ABASE);
337 +    }
338 +
339 +    private static final void relaxedSetTabAt(Node[] tab, int i, Node v) {
340 +        UNSAFE.putObject(tab, ((long)i<<ASHIFT)+ABASE, v);
341 +    }
342 +
343      /* ---------------- Access and update operations -------------- */
344  
345 <    /** Implements get and containsKey **/
345 >    /** Implementation for get and containsKey **/
346      private final Object internalGet(Object k) {
347          int h = spread(k.hashCode());
348          Node[] tab = table;
# Line 341 | Line 355 | public class ConcurrentHashMapV8<K, V>
355                      if (ev != null && ek != null && (k == ek || k.equals(ek)))
356                          return ev;
357                  }
358 <                if (eh < 0) { // bin was moved during resize
358 >                else if (eh < 0) { // bin was moved during resize
359                      tab = (Node[])e.key;
360                      continue retry;
361                  }
# Line 352 | Line 366 | public class ConcurrentHashMapV8<K, V>
366          return null;
367      }
368  
369 <    /** Implements put and putIfAbsent **/
369 >    /** Implementation for put and putIfAbsent **/
370      private final Object internalPut(Object k, Object v, boolean replace) {
371          int h = spread(k.hashCode());
372          Object oldVal = null;  // the previous value or null if none
359        Node node = null;      // the node created if absent
373          Node[] tab = table;
374          for (;;) {
375              Node e; int i;
376              if (tab == null)
377                  tab = grow(0);
378              else if ((e = tabAt(tab, i = (tab.length - 1) & h)) == null) {
379 <                if (node == null)
367 <                    node = new Node(h, k, v, null);
368 <                if (casTabAt(tab, i, null, node))
379 >                if (casTabAt(tab, i, null, new Node(h, k, v, null)))
380                      break;
381              }
382              else if (e.hash < 0)
# Line 373 | Line 384 | public class ConcurrentHashMapV8<K, V>
384              else {
385                  boolean validated = false;
386                  boolean checkSize = false;
387 <                synchronized(e) {
387 >                synchronized (e) {
388                      Node first = e;
389                      for (;;) {
390                          Object ek, ev;
# Line 393 | Line 404 | public class ConcurrentHashMapV8<K, V>
404                          if ((e = e.next) == null) {
405                              if (tabAt(tab, i) == first) {
406                                  validated = true;
407 <                                if (node == null)
408 <                                    node = new Node(h, k, v, null);
398 <                                last.next = node;
399 <                                if (last != first)
407 >                                last.next = new Node(h, k, v, null);
408 >                                if (last != first || tab.length <= 64)
409                                      checkSize = true;
410                              }
411                              break;
# Line 433 | Line 442 | public class ConcurrentHashMapV8<K, V>
442              else {
443                  boolean validated = false;
444                  boolean deleted = false;
445 <                synchronized(e) {
445 >                synchronized (e) {
446                      Node pred = null;
447                      Node first = e;
448                      for (;;) {
# Line 476 | Line 485 | public class ConcurrentHashMapV8<K, V>
485          return oldVal;
486      }
487  
488 <    /** Implements computeIfAbsent */
488 >    /** Implementation for computeIfAbsent and compute */
489      @SuppressWarnings("unchecked")
490 <    private final V computeVal(K k, MappingFunction<? super K, ? extends V> f) {
490 >    private final V internalCompute(K k,
491 >                                    MappingFunction<? super K, ? extends V> f,
492 >                                    boolean replace) {
493          int h = spread(k.hashCode());
494          V val = null;
484        Node node = null;
495          boolean added = false;
496          boolean validated = false;
497          Node[] tab = table;
# Line 490 | Line 500 | public class ConcurrentHashMapV8<K, V>
500              if (tab == null)
501                  tab = grow(0);
502              else if ((e = tabAt(tab, i = (tab.length - 1) & h)) == null) {
503 <                if (node == null)
504 <                    node = new Node(h, k, null, null);
495 <                synchronized(node) {
503 >                Node node = new Node(h, k, null, null);
504 >                synchronized (node) {
505                      if (casTabAt(tab, i, null, node)) {
506                          validated = true;
507                          try {
# Line 510 | Line 519 | public class ConcurrentHashMapV8<K, V>
519              }
520              else if (e.hash < 0)
521                  tab = (Node[])e.key;
522 +            else if (Thread.holdsLock(e))
523 +                throw new IllegalStateException("Recursive map computation");
524              else {
525                  boolean checkSize = false;
526 <                synchronized(e) {
526 >                synchronized (e) {
527                      Node first = e;
528                      for (;;) {
529                          Object ek, ev;
# Line 522 | Line 533 | public class ConcurrentHashMapV8<K, V>
533                              (k == ek || k.equals(ek))) {
534                              if (tabAt(tab, i) == first) {
535                                  validated = true;
536 +                                if (replace && (ev = f.map(k)) != null)
537 +                                    e.val = ev;
538                                  val = (V)ev;
539                              }
540                              break;
# Line 531 | Line 544 | public class ConcurrentHashMapV8<K, V>
544                              if (tabAt(tab, i) == first) {
545                                  validated = true;
546                                  if ((val = f.map(k)) != null) {
547 <                                    if (node == null)
535 <                                        node = new Node(h, k, val, null);
536 <                                    else
537 <                                        node.val = val;
538 <                                    last.next = node;
539 <                                    if (last != first)
540 <                                        checkSize = true;
547 >                                    last.next = new Node(h, k, val, null);
548                                      added = true;
549 +                                    if (last != first || tab.length <= 64)
550 +                                        checkSize = true;
551                                  }
552                              }
553                              break;
# Line 583 | Line 592 | public class ConcurrentHashMapV8<K, V>
592                  }
593                  else {
594                      boolean validated = false;
595 <                    synchronized(e) {
595 >                    synchronized (e) {
596                          int idx = e.hash & mask;
597                          Node lastRun = e;
598                          for (Node p = e.next; p != null; p = p.next) {
# Line 595 | Line 604 | public class ConcurrentHashMapV8<K, V>
604                          }
605                          if (tabAt(tab, i) == e) {
606                              validated = true;
607 <                            setTabAt(nextTab, idx, lastRun);
607 >                            relaxedSetTabAt(nextTab, idx, lastRun);
608                              for (Node p = e; p != lastRun; p = p.next) {
609                                  int h = p.hash;
610                                  int j = h & mask;
611 <                                Object pk = p.key, pv = p.val;
612 <                                Node r = tabAt(nextTab, j);
613 <                                setTabAt(nextTab, j, new Node(h, pk, pv, r));
611 >                                Node r = relaxedTabAt(nextTab, j);
612 >                                relaxedSetTabAt(nextTab, j,
613 >                                                new Node(h, p.key, p.val, r));
614                              }
615                              setTabAt(tab, i, fwd);
616                          }
# Line 623 | Line 632 | public class ConcurrentHashMapV8<K, V>
632       * @return current table
633       */
634      private final Node[] grow(int sizeHint) {
626        Node[] tab;
635          if (resizing == 0 &&
636              UNSAFE.compareAndSwapInt(this, resizingOffset, 0, 1)) {
637              try {
638                  for (;;) {
639                      int cap, n;
640 <                    if ((tab = table) == null) {
640 >                    Node[] tab = table;
641 >                    if (tab == null) {
642                          int c = initCap;
643                          if (c < sizeHint)
644                              c = sizeHint;
# Line 653 | Line 662 | public class ConcurrentHashMapV8<K, V>
662                      if (tab != null)
663                          transfer(tab, nextTab);
664                      table = nextTab;
665 <                    if (tab == null || counter.sum() < threshold) {
666 <                        tab = nextTab;
665 >                    if (tab == null || cap >= MAXIMUM_CAPACITY ||
666 >                        (sizeHint > 0 && cap >= sizeHint) ||
667 >                        counter.sum() < threshold)
668                          break;
659                    }
669                  }
670              } finally {
671                  resizing = 0;
672              }
673          }
674 <        else if ((tab = table) == null)
674 >        else if (table == null)
675              Thread.yield(); // lost initialization race; just spin
676 <        return tab;
676 >        return table;
677      }
678  
679      /**
680 <     * Implements putAll and constructor with Map argument. Tries to
681 <     * first override initial capacity or grow (once) based on map
682 <     * size to pre-allocate table space.
680 >     * Implementation for putAll and constructor with Map
681 >     * argument. Tries to first override initial capacity or grow
682 >     * based on map size to pre-allocate table space.
683       */
684      private final void internalPutAll(Map<? extends K, ? extends V> m) {
685          int s = m.size();
686 <        grow((s >= (MAXIMUM_CAPACITY >>> 1))? s : s + (s >>> 1));
686 >        grow((s >= (MAXIMUM_CAPACITY >>> 1)) ? s : s + (s >>> 1));
687          for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
688              Object k = e.getKey();
689              Object v = e.getValue();
# Line 685 | Line 694 | public class ConcurrentHashMapV8<K, V>
694      }
695  
696      /**
697 <     * Implements clear. Steps through each bin, removing all nodes.
697 >     * Implementation for clear. Steps through each bin, removing all nodes.
698       */
699      private final void internalClear() {
700 <        long delta = 0L; // negative of number of deletions
700 >        long deletions = 0L;
701          int i = 0;
702          Node[] tab = table;
703          while (tab != null && i < tab.length) {
# Line 699 | Line 708 | public class ConcurrentHashMapV8<K, V>
708                  tab = (Node[])e.key;
709              else {
710                  boolean validated = false;
711 <                synchronized(e) {
711 >                synchronized (e) {
712                      if (tabAt(tab, i) == e) {
713                          validated = true;
714                          do {
715                              if (e.val != null) {
716                                  e.val = null;
717 <                                --delta;
717 >                                ++deletions;
718                              }
719                          } while ((e = e.next) != null);
720                          setTabAt(tab, i, null);
721                      }
722                  }
723 <                if (validated)
723 >                if (validated) {
724                      ++i;
725 +                    if (deletions > THRESHOLD_OFFSET) { // bound lag in counts
726 +                        counter.add(-deletions);
727 +                        deletions = 0L;
728 +                    }
729 +                }
730              }
731          }
732 <        counter.add(delta);
732 >        if (deletions != 0L)
733 >            counter.add(-deletions);
734      }
735  
736      /**
# Line 894 | Line 909 | public class ConcurrentHashMapV8<K, V>
909       * nonpositive.
910       */
911      public ConcurrentHashMapV8(int initialCapacity,
912 <                             float loadFactor, int concurrencyLevel) {
912 >                               float loadFactor, int concurrencyLevel) {
913          if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
914              throw new IllegalArgumentException();
915          this.initCap = initialCapacity;
# Line 962 | Line 977 | public class ConcurrentHashMapV8<K, V>
977       * @return {@code true} if this map contains no key-value mappings
978       */
979      public boolean isEmpty() {
980 <        return counter.sum() == 0L;
980 >        return counter.sum() <= 0L; // ignore transient negative values
981      }
982  
983      /**
# Line 974 | Line 989 | public class ConcurrentHashMapV8<K, V>
989       */
990      public int size() {
991          long n = counter.sum();
992 <        return n >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)n;
992 >        return ((n >>> 31) == 0) ? (int)n : (n < 0L) ? 0 : Integer.MAX_VALUE;
993      }
994  
995      /**
# Line 1103 | Line 1118 | public class ConcurrentHashMapV8<K, V>
1118       *       return map.get(key);
1119       *   value = mappingFunction.map(key);
1120       *   if (value != null)
1121 <     *      return map.put(key, value);
1122 <     *   else
1108 <     *      return null;
1121 >     *      map.put(key, value);
1122 >     *   return value;
1123       * </pre>
1124       *
1125       * except that the action is performed atomically.  Some attempted
1126 <     * operations on this map by other threads may be blocked while
1127 <     * computation is in progress. Because this function is invoked
1128 <     * within atomicity control, the computation should be short and
1129 <     * simple, and must not attempt to update any other mappings of
1130 <     * this Map. The most common usage is to construct a new object
1131 <     * serving as an initial mapped value, or memoized result.
1126 >     * update operations on this map by other threads may be blocked
1127 >     * while computation is in progress, so the computation should be
1128 >     * short and simple, and must not attempt to update any other
1129 >     * mappings of this Map. The most appropriate usage is to
1130 >     * construct a new object serving as an initial mapped value, or
1131 >     * memoized result, as in:
1132 >     * <pre>{@code
1133 >     * map.computeIfAbsent(key, new MappingFunction<K, V>() {
1134 >     *   public V map(K k) { return new Value(f(k)); }};
1135 >     * }</pre>
1136       *
1137       * @param key key with which the specified value is to be associated
1138       * @param mappingFunction the function to compute a value
# Line 1123 | Line 1141 | public class ConcurrentHashMapV8<K, V>
1141       *         returned {@code null}.
1142       * @throws NullPointerException if the specified key or mappingFunction
1143       *         is null,
1144 +     * @throws IllegalStateException if the computation detectably
1145 +     *         attempts a recursive update to this map that would
1146 +     *         otherwise never complete.
1147       * @throws RuntimeException or Error if the mappingFunction does so,
1148       *         in which case the mapping is left unestablished.
1149       */
1150      public V computeIfAbsent(K key, MappingFunction<? super K, ? extends V> mappingFunction) {
1151          if (key == null || mappingFunction == null)
1152              throw new NullPointerException();
1153 <        return computeVal(key, mappingFunction);
1153 >        return internalCompute(key, mappingFunction, false);
1154 >    }
1155 >
1156 >    /**
1157 >     * Computes the value associated with the given key using the given
1158 >     * mappingFunction, and if non-null, enters it into the map.  This
1159 >     * is equivalent to
1160 >     *
1161 >     * <pre>
1162 >     *   value = mappingFunction.map(key);
1163 >     *   if (value != null)
1164 >     *      map.put(key, value);
1165 >     *   else
1166 >     *      value = map.get(key);
1167 >     *   return value;
1168 >     * </pre>
1169 >     *
1170 >     * except that the action is performed atomically.  Some attempted
1171 >     * update operations on this map by other threads may be blocked
1172 >     * while computation is in progress, so the computation should be
1173 >     * short and simple, and must not attempt to update any other
1174 >     * mappings of this Map.
1175 >     *
1176 >     * @param key key with which the specified value is to be associated
1177 >     * @param mappingFunction the function to compute a value
1178 >     * @return the current value associated with
1179 >     *         the specified key, or {@code null} if the computation
1180 >     *         returned {@code null} and the value was not otherwise present.
1181 >     * @throws NullPointerException if the specified key or mappingFunction
1182 >     *         is null,
1183 >     * @throws IllegalStateException if the computation detectably
1184 >     *         attempts a recursive update to this map that would
1185 >     *         otherwise never complete.
1186 >     * @throws RuntimeException or Error if the mappingFunction does so,
1187 >     *         in which case the mapping is unchanged.
1188 >     */
1189 >    public V compute(K key, MappingFunction<? super K, ? extends V> mappingFunction) {
1190 >        if (key == null || mappingFunction == null)
1191 >            throw new NullPointerException();
1192 >        return internalCompute(key, mappingFunction, true);
1193      }
1194  
1195      /**
# Line 1145 | Line 1205 | public class ConcurrentHashMapV8<K, V>
1205      public V remove(Object key) {
1206          if (key == null)
1207              throw new NullPointerException();
1208 <        return (V)internalReplace(key, null, null);
1208 >        return (V)internalReplace(key, null, null);
1209      }
1210  
1211      /**
# Line 1169 | Line 1229 | public class ConcurrentHashMapV8<K, V>
1229      public boolean replace(K key, V oldValue, V newValue) {
1230          if (key == null || oldValue == null || newValue == null)
1231              throw new NullPointerException();
1232 <        return internalReplace(key, newValue, oldValue) != null;
1232 >        return internalReplace(key, newValue, oldValue) != null;
1233      }
1234  
1235      /**
# Line 1183 | Line 1243 | public class ConcurrentHashMapV8<K, V>
1243      public V replace(K key, V value) {
1244          if (key == null || value == null)
1245              throw new NullPointerException();
1246 <        return (V)internalReplace(key, value, null);
1246 >        return (V)internalReplace(key, value, null);
1247      }
1248  
1249      /**
# Line 1277 | Line 1337 | public class ConcurrentHashMapV8<K, V>
1337      }
1338  
1339      /**
1340 <     * {@inheritDoc}
1340 >     * Returns the hash code value for this {@link Map}, i.e.,
1341 >     * the sum of, for each key-value pair in the map,
1342 >     * {@code key.hashCode() ^ value.hashCode()}.
1343 >     *
1344 >     * @return the hash code value for this map
1345       */
1346      public int hashCode() {
1347          return new HashIterator().mapHashCode();
1348      }
1349  
1350      /**
1351 <     * {@inheritDoc}
1351 >     * Returns a string representation of this map.  The string
1352 >     * representation consists of a list of key-value mappings (in no
1353 >     * particular order) enclosed in braces ("{@code {}}").  Adjacent
1354 >     * mappings are separated by the characters {@code ", "} (comma
1355 >     * and space).  Each key-value mapping is rendered as the key
1356 >     * followed by an equals sign ("{@code =}") followed by the
1357 >     * associated value.
1358 >     *
1359 >     * @return a string representation of this map
1360       */
1361      public String toString() {
1362          return new HashIterator().mapToString();
1363      }
1364  
1365      /**
1366 <     * {@inheritDoc}
1366 >     * Compares the specified object with this map for equality.
1367 >     * Returns {@code true} if the given object is a map with the same
1368 >     * mappings as this map.  This operation may return misleading
1369 >     * results if either map is concurrently modified during execution
1370 >     * of this method.
1371 >     *
1372 >     * @param o object to be compared for equality with this map
1373 >     * @return {@code true} if the specified object is equal to this map
1374       */
1375      public boolean equals(Object o) {
1376          if (o == this)
# Line 1471 | Line 1550 | public class ConcurrentHashMapV8<K, V>
1550      }
1551  
1552      /**
1553 <     * Reconstitutes the  instance from a
1475 <     * stream (i.e., deserializes it).
1553 >     * Reconstitutes the instance from a stream (that is, deserializes it).
1554       * @param s the stream
1555       */
1556      @SuppressWarnings("unchecked")

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines