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.2 by dl, Mon Aug 29 17:06:20 2011 UTC

# 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 172 | Line 172 | public class ConcurrentHashMapV8<K, V>
172       * complexity of access and iteration schemes that could admit
173       * out-of-order or concurrent bin transfers.
174       *
175 <     * (While not yet implemented, a similar traversal scheme can
176 <     * apply to partial traversals during partitioned aggregate
177 <     * operations. Also, read-only operations give up if ever
178 <     * forwarded to a null table, which provides support for
179 <     * shutdown-style clearing, which is also not currently
180 <     * implemented.)
175 >     * A similar traversal scheme (not yet implemented) can apply to
176 >     * partial traversals during partitioned aggregate operations.
177 >     * Also, read-only operations give up if ever forwarded to a null
178 >     * table, which provides support for shutdown-style clearing,
179 >     * which is also not currently implemented.
180       *
181       * The element count is maintained using a LongAdder, which avoids
182       * contention on updates but can encounter cache thrashing if read
183       * too frequently during concurrent updates. To avoid reading so
184 <     * often, resizing is attempted only upon adding to a bin already
185 <     * holding two or more nodes. Under the default threshold (0.75),
186 <     * and uniform hash distributions, the probability of this
184 >     * often, resizing is normally attempted only upon adding to a bin
185 >     * already holding two or more nodes. Under the default threshold
186 >     * (0.75), and uniform hash distributions, the probability of this
187       * occurring at threshold is around 13%, meaning that only about 1
188       * in 8 puts check threshold (and after resizing, many fewer do
189 <     * so). To increase the probablity that a resize occurs soon
190 <     * enough, we offset the threshold (see THRESHOLD_OFFSET) by the
191 <     * expected number of puts between checks. This is currently set
192 <     * to 8, in accord with the default load factor. In practice, this
193 <     * is rarely overridden, and in any case is close enough to other
189 >     * so). But this approximation has high variance for small table
190 >     * sizes, so we check on any collision for sizes <= 64.  Further,
191 >     * to increase the probablity that a resize occurs soon enough, we
192 >     * offset the threshold (see THRESHOLD_OFFSET) by the expected
193 >     * number of puts between checks. This is currently set to 8, in
194 >     * accord with the default load factor. In practice, this is
195 >     * rarely overridden, and in any case is close enough to other
196       * plausible values not to waste dynamic probablity computation
197       * for more precision.
198       */
# Line 310 | Line 311 | public class ConcurrentHashMapV8<K, V>
311       * implicitly bounds-checked, relying on the invariants that tab
312       * arrays have non-zero size, and all indices are masked with
313       * (tab.length - 1) which is never negative and always less than
314 <     * length. The only other usage is in HashIterator.advance, which
315 <     * performs explicit checks.
314 >     * length. The "relaxed" non-volatile forms are used only during
315 >     * table initialization. The only other usage is in
316 >     * HashIterator.advance, which performs explicit checks.
317       */
318  
319      static final Node tabAt(Node[] tab, int i) { // used in HashIterator
# Line 326 | Line 328 | public class ConcurrentHashMapV8<K, V>
328          UNSAFE.putObjectVolatile(tab, ((long)i<<ASHIFT)+ABASE, v);
329      }
330  
331 +    private static final Node relaxedTabAt(Node[] tab, int i) {
332 +        return (Node)UNSAFE.getObject(tab, ((long)i<<ASHIFT)+ABASE);
333 +    }
334 +
335 +    private static final void relaxedSetTabAt(Node[] tab, int i, Node v) {
336 +        UNSAFE.putObject(tab, ((long)i<<ASHIFT)+ABASE, v);
337 +    }
338 +
339      /* ---------------- Access and update operations -------------- */
340  
341 <    /** Implements get and containsKey **/
342 <    private final Object internalGet(Object k) {
341 >    /** Implementation for get and containsKey **/
342 >   private final Object internalGet(Object k) {
343          int h = spread(k.hashCode());
344          Node[] tab = table;
345          retry: while (tab != null) {
# Line 341 | Line 351 | public class ConcurrentHashMapV8<K, V>
351                      if (ev != null && ek != null && (k == ek || k.equals(ek)))
352                          return ev;
353                  }
354 <                if (eh < 0) { // bin was moved during resize
354 >                else if (eh < 0) { // bin was moved during resize
355                      tab = (Node[])e.key;
356                      continue retry;
357                  }
# Line 352 | Line 362 | public class ConcurrentHashMapV8<K, V>
362          return null;
363      }
364  
365 <    /** Implements put and putIfAbsent **/
365 >    /** Implementation for put and putIfAbsent **/
366      private final Object internalPut(Object k, Object v, boolean replace) {
367          int h = spread(k.hashCode());
368          Object oldVal = null;  // the previous value or null if none
359        Node node = null;      // the node created if absent
369          Node[] tab = table;
370          for (;;) {
371              Node e; int i;
372              if (tab == null)
373                  tab = grow(0);
374              else if ((e = tabAt(tab, i = (tab.length - 1) & h)) == null) {
375 <                if (node == null)
367 <                    node = new Node(h, k, v, null);
368 <                if (casTabAt(tab, i, null, node))
375 >                if (casTabAt(tab, i, null, new Node(h, k, v, null)))
376                      break;
377              }
378              else if (e.hash < 0)
# Line 393 | Line 400 | public class ConcurrentHashMapV8<K, V>
400                          if ((e = e.next) == null) {
401                              if (tabAt(tab, i) == first) {
402                                  validated = true;
403 <                                if (node == null)
404 <                                    node = new Node(h, k, v, null);
398 <                                last.next = node;
399 <                                if (last != first)
403 >                                last.next = new Node(h, k, v, null);
404 >                                if (last != first || tab.length <= 64)
405                                      checkSize = true;
406                              }
407                              break;
# Line 476 | Line 481 | public class ConcurrentHashMapV8<K, V>
481          return oldVal;
482      }
483  
484 <    /** Implements computeIfAbsent */
484 >    /** Implementation for computeIfAbsent and compute */
485      @SuppressWarnings("unchecked")
486 <    private final V computeVal(K k, MappingFunction<? super K, ? extends V> f) {
486 >    private final V internalCompute(K k,
487 >                                    MappingFunction<? super K, ? extends V> f,
488 >                                    boolean replace) {
489          int h = spread(k.hashCode());
490          V val = null;
484        Node node = null;
491          boolean added = false;
492          boolean validated = false;
493          Node[] tab = table;
# Line 490 | Line 496 | public class ConcurrentHashMapV8<K, V>
496              if (tab == null)
497                  tab = grow(0);
498              else if ((e = tabAt(tab, i = (tab.length - 1) & h)) == null) {
499 <                if (node == null)
494 <                    node = new Node(h, k, null, null);
499 >                Node node = new Node(h, k, null, null);
500                  synchronized(node) {
501                      if (casTabAt(tab, i, null, node)) {
502                          validated = true;
# Line 522 | Line 527 | public class ConcurrentHashMapV8<K, V>
527                              (k == ek || k.equals(ek))) {
528                              if (tabAt(tab, i) == first) {
529                                  validated = true;
530 +                                if (replace && (ev = f.map(k)) != null)
531 +                                    e.val = ev;
532                                  val = (V)ev;
533                              }
534                              break;
# Line 531 | Line 538 | public class ConcurrentHashMapV8<K, V>
538                              if (tabAt(tab, i) == first) {
539                                  validated = true;
540                                  if ((val = f.map(k)) != null) {
541 <                                    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;
541 >                                    last.next = new Node(h, k, val, null);
542                                      added = true;
543 +                                    if (last != first || tab.length <= 64)
544 +                                        checkSize = true;
545                                  }
546                              }
547                              break;
# Line 595 | Line 598 | public class ConcurrentHashMapV8<K, V>
598                          }
599                          if (tabAt(tab, i) == e) {
600                              validated = true;
601 <                            setTabAt(nextTab, idx, lastRun);
601 >                            relaxedSetTabAt(nextTab, idx, lastRun);
602                              for (Node p = e; p != lastRun; p = p.next) {
603                                  int h = p.hash;
604                                  int j = h & mask;
605 <                                Object pk = p.key, pv = p.val;
606 <                                Node r = tabAt(nextTab, j);
607 <                                setTabAt(nextTab, j, new Node(h, pk, pv, r));
605 >                                Node r = relaxedTabAt(nextTab, j);
606 >                                relaxedSetTabAt(nextTab, j,
607 >                                                new Node(h, p.key, p.val, r));
608                              }
609                              setTabAt(tab, i, fwd);
610                          }
# Line 623 | Line 626 | public class ConcurrentHashMapV8<K, V>
626       * @return current table
627       */
628      private final Node[] grow(int sizeHint) {
626        Node[] tab;
629          if (resizing == 0 &&
630              UNSAFE.compareAndSwapInt(this, resizingOffset, 0, 1)) {
631              try {
632                  for (;;) {
633                      int cap, n;
634 <                    if ((tab = table) == null) {
634 >                    Node[] tab = table;
635 >                    if (tab == null) {
636                          int c = initCap;
637                          if (c < sizeHint)
638                              c = sizeHint;
# Line 653 | Line 656 | public class ConcurrentHashMapV8<K, V>
656                      if (tab != null)
657                          transfer(tab, nextTab);
658                      table = nextTab;
659 <                    if (tab == null || counter.sum() < threshold) {
660 <                        tab = nextTab;
659 >                    if (tab == null || cap >= MAXIMUM_CAPACITY ||
660 >                        (sizeHint > 0 && cap >= sizeHint) ||
661 >                        counter.sum() < threshold)
662                          break;
659                    }
663                  }
664              } finally {
665                  resizing = 0;
666              }
667          }
668 <        else if ((tab = table) == null)
668 >        else if (table == null)
669              Thread.yield(); // lost initialization race; just spin
670 <        return tab;
670 >        return table;
671      }
672  
673      /**
674 <     * Implements putAll and constructor with Map argument. Tries to
675 <     * first override initial capacity or grow (once) based on map
676 <     * size to pre-allocate table space.
674 >     * Implementation for putAll and constructor with Map
675 >     * argument. Tries to first override initial capacity or grow
676 >     * based on map size to pre-allocate table space.
677       */
678      private final void internalPutAll(Map<? extends K, ? extends V> m) {
679          int s = m.size();
# Line 685 | Line 688 | public class ConcurrentHashMapV8<K, V>
688      }
689  
690      /**
691 <     * Implements clear. Steps through each bin, removing all nodes.
691 >     * Implementation for clear. Steps through each bin, removing all nodes.
692       */
693      private final void internalClear() {
694 <        long delta = 0L; // negative of number of deletions
694 >        long deletions = 0L;
695          int i = 0;
696          Node[] tab = table;
697          while (tab != null && i < tab.length) {
# Line 705 | Line 708 | public class ConcurrentHashMapV8<K, V>
708                          do {
709                              if (e.val != null) {
710                                  e.val = null;
711 <                                --delta;
711 >                                ++deletions;
712                              }
713                          } while ((e = e.next) != null);
714                          setTabAt(tab, i, null);
715                      }
716                  }
717 <                if (validated)
717 >                if (validated) {
718                      ++i;
719 +                    if (deletions > THRESHOLD_OFFSET) { // bound lag in counts
720 +                        counter.add(-deletions);
721 +                        deletions = 0L;
722 +                    }
723 +                }
724              }
725          }
726 <        counter.add(delta);
726 >        if (deletions != 0L)
727 >            counter.add(-deletions);
728      }
729  
730      /**
# Line 962 | Line 971 | public class ConcurrentHashMapV8<K, V>
971       * @return {@code true} if this map contains no key-value mappings
972       */
973      public boolean isEmpty() {
974 <        return counter.sum() == 0L;
974 >        return counter.sum() <= 0L; // ignore transient negative values
975      }
976  
977      /**
# Line 974 | Line 983 | public class ConcurrentHashMapV8<K, V>
983       */
984      public int size() {
985          long n = counter.sum();
986 <        return n >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)n;
986 >        return n <= 0L? 0 : n >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)n;
987      }
988  
989      /**
# Line 1103 | Line 1112 | public class ConcurrentHashMapV8<K, V>
1112       *       return map.get(key);
1113       *   value = mappingFunction.map(key);
1114       *   if (value != null)
1115 <     *      return map.put(key, value);
1116 <     *   else
1108 <     *      return null;
1115 >     *      map.put(key, value);
1116 >     *   return value;
1117       * </pre>
1118       *
1119       * except that the action is performed atomically.  Some attempted
1120       * operations on this map by other threads may be blocked while
1121 <     * computation is in progress. Because this function is invoked
1122 <     * within atomicity control, the computation should be short and
1123 <     * simple, and must not attempt to update any other mappings of
1116 <     * this Map. The most common usage is to construct a new object
1121 >     * computation is in progress, so the computation should be short
1122 >     * and simple, and must not attempt to update any other mappings
1123 >     * of this Map. The most common usage is to construct a new object
1124       * serving as an initial mapped value, or memoized result.
1125       *
1126       * @param key key with which the specified value is to be associated
# Line 1129 | Line 1136 | public class ConcurrentHashMapV8<K, V>
1136      public V computeIfAbsent(K key, MappingFunction<? super K, ? extends V> mappingFunction) {
1137          if (key == null || mappingFunction == null)
1138              throw new NullPointerException();
1139 <        return computeVal(key, mappingFunction);
1139 >        return internalCompute(key, mappingFunction, false);
1140 >    }
1141 >
1142 >    /**
1143 >     * Computes the value associated with he given key using the given
1144 >     * mappingFunction, and if non-null, enters it into the map.  This
1145 >     * is equivalent to
1146 >     *
1147 >     * <pre>
1148 >     *   value = mappingFunction.map(key);
1149 >     *   if (value != null)
1150 >     *      map.put(key, value);
1151 >     *   else
1152 >     *      return map.get(key);
1153 >     * </pre>
1154 >     *
1155 >     * except that the action is performed atomically.  Some attempted
1156 >     * operations on this map by other threads may be blocked while
1157 >     * computation is in progress, so the computation should be short
1158 >     * and simple, and must not attempt to update any other mappings
1159 >     * of this Map.
1160 >     *
1161 >     * @param key key with which the specified value is to be associated
1162 >     * @param mappingFunction the function to compute a value
1163 >     * @return the current value associated with
1164 >     *         the specified key, or {@code null} if the computation
1165 >     *         returned {@code null} and the value was not otherwise present.
1166 >     * @throws NullPointerException if the specified key or mappingFunction
1167 >     *         is null,
1168 >     * @throws RuntimeException or Error if the mappingFunction does so,
1169 >     *         in which case the mapping is unchanged.
1170 >     */
1171 >    public V compute(K key, MappingFunction<? super K, ? extends V> mappingFunction) {
1172 >        if (key == null || mappingFunction == null)
1173 >            throw new NullPointerException();
1174 >        return internalCompute(key, mappingFunction, true);
1175      }
1176  
1177      /**
# Line 1277 | Line 1319 | public class ConcurrentHashMapV8<K, V>
1319      }
1320  
1321      /**
1322 <     * {@inheritDoc}
1322 >     * Returns the hash code value for this {@link Map}, i.e.,
1323 >     * the sum of, for each key-value pair in the map,
1324 >     * {@code key.hashCode() ^ value.hashCode()}.
1325 >     *
1326 >     * @return the hash code value for this map
1327       */
1328      public int hashCode() {
1329          return new HashIterator().mapHashCode();
1330      }
1331  
1332      /**
1333 <     * {@inheritDoc}
1333 >     * Returns a string representation of this map.  The string
1334 >     * representation consists of a list of key-value mappings (in no
1335 >     * particular order) enclosed in braces ("{@code {}}").  Adjacent
1336 >     * mappings are separated by the characters {@code ", "} (comma
1337 >     * and space).  Each key-value mapping is rendered as the key
1338 >     * followed by an equals sign ("{@code =}") followed by the
1339 >     * associated value.
1340 >     *
1341 >     * @return a string representation of this map
1342       */
1343      public String toString() {
1344          return new HashIterator().mapToString();
1345      }
1346  
1347      /**
1348 <     * {@inheritDoc}
1348 >     * Compares the specified object with this map for equality.
1349 >     * Returns {@code true} if the given object is a map with the same
1350 >     * mappings as this map.  This operation may return misleading
1351 >     * results if either map is concurrently modified during execution
1352 >     * of this method.
1353 >     *
1354 >     * @param o object to be compared for equality with this map
1355 >     * @return {@code true} if the specified object is equal to this map
1356       */
1357      public boolean equals(Object o) {
1358          if (o == this)

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines