1 package java.lang;
   2 
   3 import jdk.internal.misc.Unsafe;
   4 
   5 import java.util.Objects;
   6 
   7 /**
   8  * A linear-probe hash table that is lock-free for lookups and uses a single
   9  * lock for modifications.
  10  *
  11  * @author peter.levart@gmail.com
  12  */
  13 class LinearProbeHashtable<K, V> {
  14 
  15     /**
  16      * The minimum capacity, used if a lower value is implicitly specified
  17      * by either of the constructors with arguments.  The value 2 corresponds
  18      * to an expected maximum size of 1 with load factor 2/3.
  19      * MUST be a power of two.
  20      */
  21     private static final int MINIMUM_CAPACITY = 2;
  22 
  23     /**
  24      * The maximum capacity, used if a higher value is implicitly specified
  25      * by either of the constructors with arguments.
  26      * MUST be a power of two <= 1<<29.
  27      * <p>
  28      * In fact, the map can hold no more than MAXIMUM_CAPACITY-1 items
  29      * because it has to have at least one slot with the key == null
  30      * in order to avoid infinite loops in get(), put(), remove()
  31      */
  32     private static final int MAXIMUM_CAPACITY = 1 << 29;
  33 
  34     /**
  35      * A special key that is used to overwrite a key when the entry is removed
  36      */
  37     private static class Tombstone {}
  38 
  39     private static final Tombstone TOMBSTONE = new Tombstone();
  40 
  41     /**
  42      * All modifications are performed under a single lock
  43      */
  44     private final Object lock = new Object();
  45 
  46     /**
  47      * The table, resized as necessary. Length MUST always be a power of two.
  48      */
  49     private volatile Object[] table;
  50 
  51     /**
  52      * Counters of inserted and tombstoned entries (size == inserted - tombstoned).
  53      */
  54     private int inserted, tombstoned;
  55 
  56     public LinearProbeHashtable(int expectedMaxSize) {
  57         if (expectedMaxSize < 0)
  58             throw new IllegalArgumentException("expectedMaxSize is negative: "
  59                                                + expectedMaxSize);
  60         table = new Object[2 * capacity(expectedMaxSize)];
  61     }
  62 
  63     @SuppressWarnings("unchecked")
  64     public V get(Object key) {
  65         Object[] tab = table;
  66         int len = tab.length;
  67         int i = firstIndex(key, len);
  68         while (true) {
  69             Object item = getVolatile(tab, i); // 1st read key...
  70             if (key == item || (item != null && item != TOMBSTONE && key.equals(item))) {
  71                 // possible race with removing an entry can cause the value
  72                 // to be already cleared. If this happens, we know the key
  73                 // has been tombstoned just before that so null is returned
  74                 return (V) getVolatile(tab, i + 1);  // ...then value
  75             }
  76             if (item == null) {
  77                 // key was not found
  78                 return null;
  79             }
  80             i = nextIndex(i, len);
  81         }
  82     }
  83 
  84     @SuppressWarnings("unchecked")
  85     public V putIfAbsent(K key, V value) {
  86         Objects.requireNonNull(value, "value");
  87         retry:
  88         while (true) {
  89             Object[] tab = table;
  90             int len = tab.length;
  91             int i = firstIndex(key, len);
  92             // assert key != null;
  93             while (true) {
  94                 Object item = getVolatile(tab, i); // 1st read key...
  95                 if (key == item || (item != null && item != TOMBSTONE && key.equals(item))) {
  96                     Object oldValue = getVolatile(tab, i + 1); // ...then value
  97                     // possible race with removing an entry can cause oldValue
  98                     // to be already cleared. If this happens, we know the key
  99                     // has been tombstoned just before that
 100                     if (oldValue != null) {
 101                         return (V) oldValue;
 102                     }
 103                     // assert getVolatile(tab, i) == TOMBSTONE;
 104                     // continue the search and find free slot or even the value
 105                 } else if (item == null) {
 106                     // the key was not found
 107                     synchronized (lock) {
 108                         // are we still there?
 109                         if (tab != table) {
 110                             continue retry;
 111                         }
 112                         // re-read key under lock
 113                         item = tab[i];
 114                         // if the table is still the same then the key slot is
 115                         // either still empty or not
 116                         if (item == null) {
 117                             if (rehash()) {
 118                                 continue retry;
 119                             }
 120                             tab[i + 1] = value; // 1st write value...
 121                             inserted++;
 122                             putVolatile(tab, i, key); // ...then key
 123                             return null;
 124                         } else if (key == item || (item != TOMBSTONE && key.equals(item))) {
 125                             // if equal key was inserted by another thread then return
 126                             // the value
 127                             return (V) tab[i + 1];
 128                         }
 129                         // else the slot was filled with some other key so
 130                         // continue the search
 131                     }
 132                 }
 133                 i = nextIndex(i, len);
 134             }
 135         }
 136     }
 137 
 138     public V put(K key, V value) {
 139         Objects.requireNonNull(value, "value");
 140         synchronized (lock) {
 141             retry:
 142             while (true) {
 143                 Object[] tab = table;
 144                 int len = tab.length;
 145                 int i = firstIndex(key, len);
 146                 while (true) {
 147                     Object item = tab[i];
 148                     if (key == item || (item != null && key.equals(item))) {
 149                         // swap values and return the old one
 150                         @SuppressWarnings("unchecked")
 151                         V oldValue = (V) tab[i + 1];
 152                         putVolatile(tab, i + 1, value);
 153                         return oldValue;
 154                     }
 155                     if (item == null) {
 156                         if (rehash()) {
 157                             continue retry;
 158                         }
 159                         tab[i + 1] = value; // 1st write value...
 160                         inserted++;
 161                         putVolatile(tab, i, key); // ...then key
 162                         return null;
 163                     }
 164                     i = nextIndex(i, len);
 165                 }
 166             }
 167         }
 168     }
 169 
 170     @SuppressWarnings("unchecked")
 171     public V remove(K key) {
 172         retry:
 173         while (true) {
 174             Object[] tab = table;
 175             int len = tab.length;
 176             int i = firstIndex(key, len);
 177             // assert key != null;
 178             while (true) {
 179                 Object item = getVolatile(tab, i); // 1st read key...
 180                 if (key == item || (item != null && item != TOMBSTONE && key.equals(item))) {
 181                     Object oldValue = getVolatile(tab, i + 1); // ...then value
 182                     // possible race with another thread removing an entry can cause oldValue
 183                     // to be already cleared. If this happens, we know the key
 184                     // has been tombstoned just before that
 185                     if (oldValue == null) {
 186                         // assert getVolatile(tab, i) == TOMBSTONE;
 187                         return null;
 188                     }
 189                     synchronized (lock) {
 190                         // are we still there?
 191                         if (tab != table) {
 192                             continue retry;
 193                         }
 194                         // if the table is still the same then the key slot is
 195                         // either occupied by the same key or tombstoned
 196                         if (tab[i] == item) {
 197                             // re-read the value under lock
 198                             oldValue = tab[i + 1];
 199                             tab[i + 1] = null; // 1st clear the value...
 200                             tombstoned++;
 201                             putVolatile(tab, i, TOMBSTONE); // ...then tombstone the key
 202                             return (V) oldValue;
 203                         } else {
 204                             // assert tab[i] == TOMBSTONE;
 205                             return null;
 206                         }
 207                     }
 208                 } else if (item == null) {
 209                     // the key was not found
 210                     return null;
 211                 }
 212                 i = nextIndex(i, len);
 213             }
 214         }
 215     }
 216 
 217     public boolean replace(K key, V expectedValue, V newValue) {
 218         Objects.requireNonNull(expectedValue, "expectedValue");
 219         Objects.requireNonNull(newValue, "newValue");
 220         retry:
 221         while (true) {
 222             Object[] tab = table;
 223             int len = tab.length;
 224             int i = firstIndex(key, len);
 225             // assert key != null;
 226             while (true) {
 227                 Object item = getVolatile(tab, i); // 1st read key...
 228                 if (key == item || (item != null && item != TOMBSTONE && key.equals(item))) {
 229                     Object oldValue = getVolatile(tab, i + 1); // ...then value
 230                     // possible race with another thread removing an entry can cause oldValue
 231                     // to be already cleared. If this happens, we know the key
 232                     // has been tombstoned just before that
 233                     if (oldValue == null) {
 234                         return false;
 235                     }
 236                     synchronized (lock) {
 237                         // are we still there?
 238                         if (tab != table) {
 239                             continue retry;
 240                         }
 241                         // if the table is still the same then the key slot is
 242                         // either occupied by the same key or tombstoned
 243                         if (tab[i] == item) {
 244                             // re-read the value under lock
 245                             oldValue = tab[i + 1];
 246                             if (expectedValue == oldValue ||
 247                                 expectedValue.equals(oldValue)) {
 248                                 putVolatile(tab, i + 1, newValue);
 249                                 return true;
 250                             } else {
 251                                 return false;
 252                             }
 253                         } else {
 254                             // assert tab[i] == TOMBSTONE;
 255                             return false;
 256                         }
 257                     }
 258                 } else if (item == null) {
 259                     // the key was not found
 260                     return false;
 261                 }
 262                 i = nextIndex(i, len);
 263             }
 264         }
 265     }
 266 
 267     /**
 268      * Returns the appropriate capacity for the given expected maximum size.
 269      *
 270      * @throws OutOfMemoryError if {@code expectedMaxSize > MAXIMUM_CAPACITY * 2 / 3}
 271      */
 272     private static int capacity(int expectedMaxSize) {
 273         // assert expectedMaxSize >= 0;
 274         if (expectedMaxSize > MAXIMUM_CAPACITY * 2 / 3) {
 275             throw new OutOfMemoryError("Maximum capacity exceeded");
 276         }
 277         return
 278             (expectedMaxSize > MAXIMUM_CAPACITY / 3) ? MAXIMUM_CAPACITY :
 279             (expectedMaxSize <= 2 * MINIMUM_CAPACITY / 3) ? MINIMUM_CAPACITY :
 280             Integer.highestOneBit(expectedMaxSize + (expectedMaxSize << 1));
 281     }
 282 
 283     private static int firstIndex(Object key, int len) {
 284         return (key.hashCode() << 1) & (len - 1);
 285     }
 286 
 287     private static int nextIndex(int i, int len) {
 288         return (i + 2) & (len - 1);
 289     }
 290 
 291     /**
 292      * Check whether current table is long enough to accommodate for
 293      * new entry to be inserted and resize it by rehashing into new table if
 294      * not.
 295      *
 296      * @return true if table was rehashed and replaced with new table
 297      */
 298     private boolean rehash() {
 299         // assert Thread.holdsLock(lock);
 300         Object[] curTab = table;
 301         int curLen = curTab.length;
 302         if (2 * capacity(inserted + 1) > curLen) {
 303             // shrink table only half-way down to prevent excessive
 304             // rehashing in situations with repeated put/remove usage
 305             // patterns
 306             int newLen = 2 * capacity(inserted - (tombstoned / 2) + 1);
 307             Object[] newTab = new Object[newLen];
 308             for (int j = 0; j < curLen; j += 2) {
 309                 Object key = curTab[j];
 310                 if (key != null && key != TOMBSTONE) {
 311                     Object value = curTab[j + 1];
 312                     curTab[j] = null;
 313                     curTab[j + 1] = null;
 314                     // insert it in the new table
 315                     int i = firstIndex(key, newLen);
 316                     while (newTab[i] != null) {
 317                         i = nextIndex(i, newLen);
 318                     }
 319                     newTab[i] = key;
 320                     newTab[i + 1] = value;
 321                 }
 322             }
 323             inserted -= tombstoned;
 324             tombstoned = 0;
 325             table = newTab;
 326             return true;
 327         } else {
 328             return false;
 329         }
 330     }
 331 
 332     private static Object getVolatile(Object[] a, int i) {
 333         return U.getObjectVolatile(a, byteOffset(i));
 334     }
 335 
 336     private static void putVolatile(Object[] a, int i, Object x) {
 337         U.putObjectVolatile(a, byteOffset(i), x);
 338     }
 339 
 340     private static long byteOffset(int i) {
 341         return ((long) i << ASHIFT) + ABASE;
 342     }
 343 
 344     private static final Unsafe U;
 345     private static final int ABASE;
 346     private static final int ASHIFT;
 347 
 348     static {
 349         U = Unsafe.getUnsafe();
 350         ABASE = U.arrayBaseOffset(Object[].class);
 351         int scale = U.arrayIndexScale(Object[].class);
 352         if ((scale & (scale - 1)) != 0)
 353             throw new Error("array index scale not a power of two");
 354         ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
 355     }
 356 }