src/share/classes/java/util/Hashtable.java

Print this page
rev 5810 : 8006593: Performance and compatibility improvements to hash based Map implementations.
Summary: Use ThreadLocalRandom for hash seed rather than shared Random. Initialize {HashMap|Hashtable}.hashSeed only as needed. Minor optimizations.
Reviewed-by: alanb, bchristi

@@ -214,55 +214,38 @@
 
     /**
      * If {@code true} then perform alternative hashing of String keys to reduce
      * the incidence of collisions due to weak hash code calculation.
      */
-    transient boolean useAltHashing;
+    transient boolean useAltHashing = false;
 
-    // Unsafe mechanics
     /**
-    * Unsafe utilities
+     * A randomizing value associated with this instance that is applied to
+     * hash code of keys to make hash collisions harder to find.
     */
-    private static final sun.misc.Unsafe UNSAFE;
+    transient int hashSeed;
 
     /**
-    * Offset of "final" hashSeed field we must set in readObject() method.
+     * Initialize the hashing mask value.
     */
-    private static final long HASHSEED_OFFSET;
-
-     static {
-        try {
-            UNSAFE = sun.misc.Unsafe.getUnsafe();
-            HASHSEED_OFFSET = UNSAFE.objectFieldOffset(
-                Hashtable.class.getDeclaredField("hashSeed"));
-        } catch (NoSuchFieldException | SecurityException e) {
-            throw new Error("Failed to record hashSeed offset", e);
+    final boolean initHashSeedAsNeeded(int capacity) {
+        boolean currentAltHashing = useAltHashing;
+        useAltHashing = sun.misc.VM.isBooted() &&
+                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
+        boolean switching = currentAltHashing ^ useAltHashing;
+        if (switching) {
+            hashSeed = useAltHashing
+                ? sun.misc.Hashing.randomHashSeed(this)
+                : 0;
         }
+        return switching;
      }
 
-    /**
-     * A randomizing value associated with this instance that is applied to
-     * hash code of keys to make hash collisions harder to find.
-     */
-    transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this);
-
     private int hash(Object k) {
-        if (useAltHashing) {
-            if (k.getClass() == String.class) {
-                return sun.misc.Hashing.stringHash32((String) k);
-            } else {
-                int h = hashSeed ^ k.hashCode();
-
-                // This function ensures that hashCodes that differ only by
-                // constant multiples at each bit position have a bounded
-                // number of collisions (approximately 8 at default load factor).
-                h ^= (h >>> 20) ^ (h >>> 12);
-                return h ^ (h >>> 7) ^ (h >>> 4);
-             }
-        } else  {
-            return k.hashCode();
-        }
+        // We don't test for useAltHashing because hashSeed will be zero if
+        // alternative hashing is disabled.
+        return hashSeed ^ k.hashCode();
     }
 
     /**
      * Constructs a new, empty hashtable with the specified initial
      * capacity and the specified load factor.

@@ -282,12 +265,11 @@
         if (initialCapacity==0)
             initialCapacity = 1;
         this.loadFactor = loadFactor;
         table = new Entry[initialCapacity];
         threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
-        useAltHashing = sun.misc.VM.isBooted() &&
-                (initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
+        initHashSeedAsNeeded(initialCapacity);
     }
 
     /**
      * Constructs a new, empty hashtable with the specified initial capacity
      * and default load factor (0.75).

@@ -495,14 +477,11 @@
         }
         Entry<K,V>[] newMap = new Entry[newCapacity];
 
         modCount++;
         threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
-        boolean currentAltHashing = useAltHashing;
-        useAltHashing = sun.misc.VM.isBooted() &&
-                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
-        boolean rehash = currentAltHashing ^ useAltHashing;
+        boolean rehash = initHashSeedAsNeeded(newCapacity);
 
         table = newMap;
 
         for (int i = oldCapacity ; i-- > 0 ;) {
             for (Entry<K,V> old = oldMap[i] ; old != null ; ) {

@@ -997,14 +976,10 @@
          throws IOException, ClassNotFoundException
     {
         // Read in the length, threshold, and loadfactor
         s.defaultReadObject();
 
-        // set hashSeed
-        UNSAFE.putIntVolatile(this, HASHSEED_OFFSET,
-                sun.misc.Hashing.randomHashSeed(this));
-
         // Read the original length of the array and number of elements
         int origlength = s.readInt();
         int elements = s.readInt();
 
         // Compute new size with a bit of room 5% to grow but

@@ -1015,24 +990,23 @@
         if (length > elements && (length & 1) == 0)
             length--;
         if (origlength > 0 && length > origlength)
             length = origlength;
 
-        Entry<K,V>[] table = new Entry[length];
+        Entry<K,V>[] newTable = new Entry[length];
         threshold = (int) Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
         count = 0;
-        useAltHashing = sun.misc.VM.isBooted() &&
-                (length >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
+        initHashSeedAsNeeded(length);
 
         // Read the number of elements and then all the key/value objects
         for (; elements > 0; elements--) {
             K key = (K)s.readObject();
             V value = (V)s.readObject();
             // synch could be eliminated for performance
-            reconstitutionPut(table, key, value);
+            reconstitutionPut(newTable, key, value);
         }
-        this.table = table;
+        this.table = newTable;
     }
 
     /**
      * The put method used by readObject. This is provided because put
      * is overridable and should not be called in readObject since the