-
Notifications
You must be signed in to change notification settings - Fork 61
/
NonBlockingHashMap.java
1297 lines (1184 loc) · 65.1 KB
/
NonBlockingHashMap.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Written by Cliff Click and released to the public domain, as explained at
* http://creativecommons.org/licenses/publicdomain
*/
package org.cliffc.high_scale_lib;
import sun.misc.Unsafe;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* A lock-free alternate implementation of {@link java.util.concurrent.ConcurrentHashMap}
* with better scaling properties and generally lower costs to mutate the Map.
* It provides identical correctness properties as ConcurrentHashMap. All
* operations are non-blocking and multi-thread safe, including all update
* operations. {@link NonBlockingHashMap} scales substantially better than
* {@link java.util.concurrent.ConcurrentHashMap} for high update rates, even with a
* large concurrency factor. Scaling is linear up to 768 CPUs on a 768-CPU
* Azul box, even with 100% updates or 100% reads or any fraction in-between.
* Linear scaling up to all cpus has been observed on a 32-way Sun US2 box,
* 32-way Sun Niagra box, 8-way Intel box and a 4-way Power box.
*
* This class obeys the same functional specification as {@link
* java.util.Hashtable}, and includes versions of methods corresponding to
* each method of <tt>Hashtable</tt>. However, even though all operations are
* thread-safe, operations do <em>not</em> entail locking and there is
* <em>not</em> any support for locking the entire table in a way that
* prevents all access. This class is fully interoperable with
* <tt>Hashtable</tt> in programs that rely on its thread safety but not on
* its synchronization details.
*
* <p> Operations (including <tt>put</tt>) generally do not block, so may
* overlap with other update operations (including other <tt>puts</tt> and
* <tt>removes</tt>). Retrievals reflect the results of the most recently
* <em>completed</em> update operations holding upon their onset. For
* aggregate operations such as <tt>putAll</tt>, concurrent retrievals may
* reflect insertion or removal of only some entries. Similarly, Iterators
* and Enumerations return elements reflecting the state of the hash table at
* some point at or since the creation of the iterator/enumeration. They do
* <em>not</em> throw {@link ConcurrentModificationException}. However,
* iterators are designed to be used by only one thread at a time.
*
* <p> Very full tables, or tables with high reprobe rates may trigger an
* internal resize operation to move into a larger table. Resizing is not
* terribly expensive, but it is not free either; during resize operations
* table throughput may drop somewhat. All threads that visit the table
* during a resize will 'help' the resizing but will still be allowed to
* complete their operation before the resize is finished (i.e., a simple
* 'get' operation on a million-entry table undergoing resizing will not need
* to block until the entire million entries are copied).
*
* <p>This class and its views and iterators implement all of the
* <em>optional</em> methods of the {@link Map} and {@link Iterator}
* interfaces.
*
* <p> Like {@link Hashtable} but unlike {@link HashMap}, this class
* does <em>not</em> allow <tt>null</tt> to be used as a key or value.
*
*
* @since 1.5
* @author Cliff Click
* @param <TypeK> the type of keys maintained by this map
* @param <TypeV> the type of mapped values
*
* @version 1.1.2
* @author Prashant Deva - moved hash() function out of get_impl() so it is
* not calculated multiple times.
*/
public class NonBlockingHashMap<TypeK, TypeV>
extends AbstractMap<TypeK, TypeV>
implements ConcurrentMap<TypeK, TypeV>, Cloneable, Serializable {
private static final long serialVersionUID = 1234123412341234123L;
private static final int REPROBE_LIMIT=10; // Too many reprobes then force a table-resize
// --- Bits to allow Unsafe access to arrays
private static final Unsafe _unsafe = UtilUnsafe.getUnsafe();
private static final int _Obase = _unsafe.arrayBaseOffset(Object[].class);
private static final int _Oscale = _unsafe.arrayIndexScale(Object[].class);
private static long rawIndex(final Object[] ary, final int idx) {
assert idx >= 0 && idx < ary.length;
return _Obase + idx * _Oscale;
}
// --- Setup to use Unsafe
private static final long _kvs_offset;
static { // <clinit>
Field f = null;
try { f = NonBlockingHashMap.class.getDeclaredField("_kvs"); }
catch( java.lang.NoSuchFieldException e ) { throw new RuntimeException(e); }
_kvs_offset = _unsafe.objectFieldOffset(f);
}
private final boolean CAS_kvs( final Object[] oldkvs, final Object[] newkvs ) {
return _unsafe.compareAndSwapObject(this, _kvs_offset, oldkvs, newkvs );
}
// --- Adding a 'prime' bit onto Values via wrapping with a junk wrapper class
private static final class Prime {
final Object _V;
Prime( Object V ) { _V = V; }
static Object unbox( Object V ) { return V instanceof Prime ? ((Prime)V)._V : V; }
}
// --- hash ----------------------------------------------------------------
// Helper function to spread lousy hashCodes
private static final int hash(final Object key) {
int h = key.hashCode(); // The real hashCode call
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
// --- The Hash Table --------------------
// Slot 0 is always used for a 'CHM' entry below to hold the interesting
// bits of the hash table. Slot 1 holds full hashes as an array of ints.
// Slots {2,3}, {4,5}, etc hold {Key,Value} pairs. The entire hash table
// can be atomically replaced by CASing the _kvs field.
//
// Why is CHM buried inside the _kvs Object array, instead of the other way
// around? The CHM info is used during resize events and updates, but not
// during standard 'get' operations. I assume 'get' is much more frequent
// than 'put'. 'get' can skip the extra indirection of skipping through the
// CHM to reach the _kvs array.
private transient Object[] _kvs;
private static final CHM chm (Object[] kvs) { return (CHM )kvs[0]; }
private static final int[] hashes(Object[] kvs) { return (int[])kvs[1]; }
// Number of K,V pairs in the table
private static final int len(Object[] kvs) { return (kvs.length-2)>>1; }
// Time since last resize
private transient long _last_resize_milli;
// --- Minimum table size ----------------
// Pick size 8 K/V pairs, which turns into (8*2+2)*4+12 = 84 bytes on a
// standard 32-bit HotSpot, and (8*2+2)*8+12 = 156 bytes on 64-bit Azul.
private static final int MIN_SIZE_LOG=3; //
private static final int MIN_SIZE=(1<<MIN_SIZE_LOG); // Must be power of 2
// --- Sentinels -------------------------
// No-Match-Old - putIfMatch does updates only if it matches the old value,
// and NO_MATCH_OLD basically counts as a wildcard match.
private static final Object NO_MATCH_OLD = new Object(); // Sentinel
// Match-Any-not-null - putIfMatch does updates only if it find a real old
// value.
private static final Object MATCH_ANY = new Object(); // Sentinel
// This K/V pair has been deleted (but the Key slot is forever claimed).
// The same Key can be reinserted with a new value later.
private static final Object TOMBSTONE = new Object();
// Prime'd or box'd version of TOMBSTONE. This K/V pair was deleted, then a
// table resize started. The K/V pair has been marked so that no new
// updates can happen to the old table (and since the K/V pair was deleted
// nothing was copied to the new table).
private static final Prime TOMBPRIME = new Prime(TOMBSTONE);
// --- key,val -------------------------------------------------------------
// Access K,V for a given idx
//
// Note that these are static, so that the caller is forced to read the _kvs
// field only once, and share that read across all key/val calls - lest the
// _kvs field move out from under us and back-to-back key & val calls refer
// to different _kvs arrays.
private static final Object key(Object[] kvs,int idx) { return kvs[(idx<<1)+2]; }
private static final Object val(Object[] kvs,int idx) { return kvs[(idx<<1)+3]; }
private static final boolean CAS_key( Object[] kvs, int idx, Object old, Object key ) {
return _unsafe.compareAndSwapObject( kvs, rawIndex(kvs,(idx<<1)+2), old, key );
}
private static final boolean CAS_val( Object[] kvs, int idx, Object old, Object val ) {
return _unsafe.compareAndSwapObject( kvs, rawIndex(kvs,(idx<<1)+3), old, val );
}
// --- dump ----------------------------------------------------------------
/** Verbose printout of table internals, useful for debugging. */
public final void print() {
System.out.println("=========");
print2(_kvs);
System.out.println("=========");
}
// print the entire state of the table
private final void print( Object[] kvs ) {
for( int i=0; i<len(kvs); i++ ) {
Object K = key(kvs,i);
if( K != null ) {
String KS = (K == TOMBSTONE) ? "XXX" : K.toString();
Object V = val(kvs,i);
Object U = Prime.unbox(V);
String p = (V==U) ? "" : "prime_";
String US = (U == TOMBSTONE) ? "tombstone" : U.toString();
System.out.println(""+i+" ("+KS+","+p+US+")");
}
}
Object[] newkvs = chm(kvs)._newkvs; // New table, if any
if( newkvs != null ) {
System.out.println("----");
print(newkvs);
}
}
// print only the live values, broken down by the table they are in
private final void print2( Object[] kvs) {
for( int i=0; i<len(kvs); i++ ) {
Object key = key(kvs,i);
Object val = val(kvs,i);
Object U = Prime.unbox(val);
if( key != null && key != TOMBSTONE && // key is sane
val != null && U != TOMBSTONE ) { // val is sane
String p = (val==U) ? "" : "prime_";
System.out.println(""+i+" ("+key+","+p+val+")");
}
}
Object[] newkvs = chm(kvs)._newkvs; // New table, if any
if( newkvs != null ) {
System.out.println("----");
print2(newkvs);
}
}
// Count of reprobes
private transient Counter _reprobes = new Counter();
/** Get and clear the current count of reprobes. Reprobes happen on key
* collisions, and a high reprobe rate may indicate a poor hash function or
* weaknesses in the table resizing function.
* @return the count of reprobes since the last call to {@link #reprobes}
* or since the table was created. */
public long reprobes() { long r = _reprobes.get(); _reprobes = new Counter(); return r; }
// --- reprobe_limit -----------------------------------------------------
// Heuristic to decide if we have reprobed toooo many times. Running over
// the reprobe limit on a 'get' call acts as a 'miss'; on a 'put' call it
// can trigger a table resize. Several places must have exact agreement on
// what the reprobe_limit is, so we share it here.
private static final int reprobe_limit( int len ) {
return REPROBE_LIMIT + (len>>2);
}
// --- NonBlockingHashMap --------------------------------------------------
// Constructors
/** Create a new NonBlockingHashMap with default minimum size (currently set
* to 8 K/V pairs or roughly 84 bytes on a standard 32-bit JVM). */
public NonBlockingHashMap( ) { this(MIN_SIZE); }
/** Create a new NonBlockingHashMap with initial room for the given number of
* elements, thus avoiding internal resizing operations to reach an
* appropriate size. Large numbers here when used with a small count of
* elements will sacrifice space for a small amount of time gained. The
* initial size will be rounded up internally to the next larger power of 2. */
public NonBlockingHashMap( final int initial_sz ) { initialize(initial_sz); }
private final void initialize( int initial_sz ) {
if( initial_sz < 0 ) throw new IllegalArgumentException();
int i; // Convert to next largest power-of-2
if( initial_sz > 1024*1024 ) initial_sz = 1024*1024;
for( i=MIN_SIZE_LOG; (1<<i) < (initial_sz<<2); i++ ) ;
// Double size for K,V pairs, add 1 for CHM and 1 for hashes
_kvs = new Object[((1<<i)<<1)+2];
_kvs[0] = new CHM(new Counter()); // CHM in slot 0
_kvs[1] = new int[1<<i]; // Matching hash entries
_last_resize_milli = System.currentTimeMillis();
}
// Version for subclassed readObject calls, to be called after the defaultReadObject
protected final void initialize() { initialize(MIN_SIZE); }
// --- wrappers ------------------------------------------------------------
/** Returns the number of key-value mappings in this map.
* @return the number of key-value mappings in this map */
@Override
public int size ( ) { return chm(_kvs).size(); }
/** Returns <tt>size() == 0</tt>.
* @return <tt>size() == 0</tt> */
@Override
public boolean isEmpty ( ) { return size() == 0; }
/** Tests if the key in the table using the <tt>equals</tt> method.
* @return <tt>true</tt> if the key is in the table using the <tt>equals</tt> method
* @throws NullPointerException if the specified key is null */
@Override
public boolean containsKey( Object key ) { return get(key) != null; }
/** Legacy method testing if some key maps into the specified value in this
* table. This method is identical in functionality to {@link
* #containsValue}, 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 val a value to search for
* @return <tt>true</tt> if this map maps one or more keys to the specified value
* @throws NullPointerException if the specified value is null */
public boolean contains ( Object val ) { return containsValue(val); }
/** Maps the specified key to the specified value in the table. Neither key
* nor value can be null.
* <p> The value can be retrieved by calling {@link #get} with a key that is
* equal to the original key.
* @param key key with which the specified value is to be associated
* @param val value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>
* @throws NullPointerException if the specified key or value is null */
@Override
public TypeV put ( TypeK key, TypeV val ) { return putIfMatch( key, val, NO_MATCH_OLD); }
/** Atomically, do a {@link #put} if-and-only-if the key is not mapped.
* Useful to ensure that only a single mapping for the key exists, even if
* many threads are trying to create the mapping in parallel.
* @return the previous value associated with the specified key,
* or <tt>null</tt> if there was no mapping for the key
* @throws NullPointerException if the specified key or value is null */
public TypeV putIfAbsent( TypeK key, TypeV val ) { return putIfMatch( key, val, TOMBSTONE ); }
/** Removes the key (and its corresponding value) from this map.
* This method does nothing if the key is not in the map.
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>
* @throws NullPointerException if the specified key is null */
@Override
public TypeV remove ( Object key ) { return putIfMatch( key,TOMBSTONE, NO_MATCH_OLD); }
/** Atomically do a {@link #remove(Object)} if-and-only-if the key is mapped
* to a value which is <code>equals</code> to the given value.
* @throws NullPointerException if the specified key or value is null */
public boolean remove ( Object key,Object val ) {
final Object out = putIfMatch(key, TOMBSTONE, val);
return (val == null) ? out == val : val.equals(out);
}
/** Atomically do a <code>put(key,val)</code> if-and-only-if the key is
* mapped to some value already.
* @throws NullPointerException if the specified key or value is null */
public TypeV replace ( TypeK key, TypeV val ) { return putIfMatch( key, val,MATCH_ANY ); }
/** Atomically do a <code>put(key,newValue)</code> if-and-only-if the key is
* mapped a value which is <code>equals</code> to <code>oldValue</code>.
* @throws NullPointerException if the specified key or value is null */
public boolean replace ( TypeK key, TypeV oldValue, TypeV newValue ) {
final Object out = putIfMatch(key, newValue, oldValue);
return (oldValue == null) ? out == oldValue : oldValue.equals(out);
}
private final TypeV putIfMatch( Object key, Object newVal, Object oldVal ) {
if (oldVal == null || newVal == null) throw new NullPointerException();
final Object res = putIfMatch( this, _kvs, key, newVal, oldVal );
assert !(res instanceof Prime);
assert res != null;
return res == TOMBSTONE ? null : (TypeV)res;
}
/** Copies all of the mappings from the specified map to this one, replacing
* any existing mappings.
* @param m mappings to be stored in this map */
@Override
public void putAll(Map<? extends TypeK, ? extends TypeV> m) {
for (Map.Entry<? extends TypeK, ? extends TypeV> e : m.entrySet())
put(e.getKey(), e.getValue());
}
/** Removes all of the mappings from this map. */
@Override
public void clear() { // Smack a new empty table down
Object[] newkvs = new NonBlockingHashMap(MIN_SIZE)._kvs;
while( !CAS_kvs(_kvs,newkvs) ) // Spin until the clear works
;
}
/** Returns <tt>true</tt> if this Map maps one or more keys to the specified
* value. <em>Note</em>: This method requires a full internal traversal of the
* hash table and is much slower than {@link #containsKey}.
* @param val value whose presence in this map is to be tested
* @return <tt>true</tt> if this map maps one or more keys to the specified value
* @throws NullPointerException if the specified value is null */
@Override
public boolean containsValue( final Object val ) {
if( val == null ) throw new NullPointerException();
for( TypeV V : values() )
if( V == val || V.equals(val) )
return true;
return false;
}
// This function is supposed to do something for Hashtable, and the JCK
// tests hang until it gets called... by somebody ... for some reason,
// any reason....
protected void rehash() {
}
/**
* Creates a shallow copy of this hashtable. All the structure of the
* hashtable itself is copied, but the keys and values are not cloned.
* This is a relatively expensive operation.
*
* @return a clone of the hashtable.
*/
@Override
public Object clone() {
try {
// Must clone, to get the class right; NBHM might have been
// extended so it would be wrong to just make a new NBHM.
NonBlockingHashMap<TypeK,TypeV> t = (NonBlockingHashMap<TypeK,TypeV>) super.clone();
// But I don't have an atomic clone operation - the underlying _kvs
// structure is undergoing rapid change. If I just clone the _kvs
// field, the CHM in _kvs[0] won't be in sync.
//
// Wipe out the cloned array (it was shallow anyways).
t.clear();
// Now copy sanely
for( TypeK K : keySet() ) {
final TypeV V = get(K); // Do an official 'get'
t.put(K,V);
}
return t;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
/**
* Returns a string representation of this map. The string representation
* consists of a list of key-value mappings in the order returned by the
* map's <tt>entrySet</tt> view's iterator, enclosed in braces
* (<tt>"{}"</tt>). Adjacent mappings are separated by the characters
* <tt>", "</tt> (comma and space). Each key-value mapping is rendered as
* the key followed by an equals sign (<tt>"="</tt>) followed by the
* associated value. Keys and values are converted to strings as by
* {@link String#valueOf(Object)}.
*
* @return a string representation of this map
*/
@Override
public String toString() {
Iterator<Entry<TypeK,TypeV>> i = entrySet().iterator();
if( !i.hasNext())
return "{}";
StringBuilder sb = new StringBuilder();
sb.append('{');
for (;;) {
Entry<TypeK,TypeV> e = i.next();
TypeK key = e.getKey();
TypeV value = e.getValue();
sb.append(key == this ? "(this Map)" : key);
sb.append('=');
sb.append(value == this ? "(this Map)" : value);
if( !i.hasNext())
return sb.append('}').toString();
sb.append(", ");
}
}
// --- keyeq ---------------------------------------------------------------
// Check for key equality. Try direct pointer compare first, then see if
// the hashes are unequal (fast negative test) and finally do the full-on
// 'equals' v-call.
private static boolean keyeq( Object K, Object key, int[] hashes, int hash, int fullhash ) {
return
K==key || // Either keys match exactly OR
// hash exists and matches? hash can be zero during the install of a
// new key/value pair.
((hashes[hash] == 0 || hashes[hash] == fullhash) &&
// Do not call the users' "equals()" call with a Tombstone, as this can
// surprise poorly written "equals()" calls that throw exceptions
// instead of simply returning false.
K != TOMBSTONE && // Do not call users' equals call with a Tombstone
// Do the match the hard way - with the users' key being the loop-
// invariant "this" pointer. I could have flipped the order of
// operands (since equals is commutative), but I'm making mega-morphic
// v-calls in a reprobing loop and nailing down the 'this' argument
// gives both the JIT and the hardware a chance to prefetch the call target.
key.equals(K)); // Finally do the hard match
}
// --- get -----------------------------------------------------------------
/** Returns the value to which the specified key is mapped, or {@code null}
* if this map contains no mapping for the key.
* <p>More formally, if this map contains a mapping from a key {@code k} to
* a value {@code v} such that {@code key.equals(k)}, then this method
* returns {@code v}; otherwise it returns {@code null}. (There can be at
* most one such mapping.)
* @throws NullPointerException if the specified key is null */
// Never returns a Prime nor a Tombstone.
@Override
public TypeV get( Object key ) {
final int fullhash= hash (key); // throws NullPointerException if key is null
final Object V = get_impl(this,_kvs,key,fullhash);
assert !(V instanceof Prime); // Never return a Prime
return (TypeV)V;
}
private static final Object get_impl( final NonBlockingHashMap topmap, final Object[] kvs, final Object key, final int fullhash ) {
final int len = len (kvs); // Count of key/value pairs, reads kvs.length
final CHM chm = chm (kvs); // The CHM, for a volatile read below; reads slot 0 of kvs
final int[] hashes=hashes(kvs); // The memoized hashes; reads slot 1 of kvs
int idx = fullhash & (len-1); // First key hash
// Main spin/reprobe loop, looking for a Key hit
int reprobe_cnt=0;
while( true ) {
// Probe table. Each read of 'val' probably misses in cache in a big
// table; hopefully the read of 'key' then hits in cache.
final Object K = key(kvs,idx); // Get key before volatile read, could be null
final Object V = val(kvs,idx); // Get value before volatile read, could be null or Tombstone or Prime
if( K == null ) return null; // A clear miss
// We need a volatile-read here to preserve happens-before semantics on
// newly inserted Keys. If the Key body was written just before inserting
// into the table a Key-compare here might read the uninitalized Key body.
// Annoyingly this means we have to volatile-read before EACH key compare.
// .
// We also need a volatile-read between reading a newly inserted Value
// and returning the Value (so the user might end up reading the stale
// Value contents). Same problem as with keys - and the one volatile
// read covers both.
final Object[] newkvs = chm._newkvs; // VOLATILE READ before key compare
// Key-compare
if( keyeq(K,key,hashes,idx,fullhash) ) {
// Key hit! Check for no table-copy-in-progress
if( !(V instanceof Prime) ) // No copy?
return (V == TOMBSTONE) ? null : V; // Return the value
// Key hit - but slot is (possibly partially) copied to the new table.
// Finish the copy & retry in the new table.
return get_impl(topmap,chm.copy_slot_and_check(topmap,kvs,idx,key),key,fullhash); // Retry in the new table
}
// get and put must have the same key lookup logic! But only 'put'
// needs to force a table-resize for a too-long key-reprobe sequence.
// Check for too-many-reprobes on get - and flip to the new table.
if( ++reprobe_cnt >= reprobe_limit(len) || // too many probes
K == TOMBSTONE ) // found a TOMBSTONE key, means no more keys in this table
return newkvs == null ? null : get_impl(topmap,topmap.help_copy(newkvs),key,fullhash); // Retry in the new table
idx = (idx+1)&(len-1); // Reprobe by 1! (could now prefetch)
}
}
// --- putIfMatch ---------------------------------------------------------
// Put, Remove, PutIfAbsent, etc. Return the old value. If the returned
// value is equal to expVal (or expVal is NO_MATCH_OLD) then the put can be
// assumed to work (although might have been immediately overwritten). Only
// the path through copy_slot passes in an expected value of null, and
// putIfMatch only returns a null if passed in an expected null.
private static final Object putIfMatch( final NonBlockingHashMap topmap, final Object[] kvs, final Object key, final Object putval, final Object expVal ) {
assert putval != null;
assert !(putval instanceof Prime);
assert !(expVal instanceof Prime);
final int fullhash = hash (key); // throws NullPointerException if key null
final int len = len (kvs); // Count of key/value pairs, reads kvs.length
final CHM chm = chm (kvs); // Reads kvs[0]
final int[] hashes = hashes(kvs); // Reads kvs[1], read before kvs[0]
int idx = fullhash & (len-1);
// ---
// Key-Claim stanza: spin till we can claim a Key (or force a resizing).
int reprobe_cnt=0;
Object K=null, V=null;
Object[] newkvs=null;
while( true ) { // Spin till we get a Key slot
V = val(kvs,idx); // Get old value (before volatile read below!)
K = key(kvs,idx); // Get current key
if( K == null ) { // Slot is free?
// Found an empty Key slot - which means this Key has never been in
// this table. No need to put a Tombstone - the Key is not here!
if( putval == TOMBSTONE ) return putval; // Not-now & never-been in this table
// Claim the null key-slot
if( CAS_key(kvs,idx, null, key ) ) { // Claim slot for Key
chm._slots.add(1); // Raise key-slots-used count
hashes[idx] = fullhash; // Memoize fullhash
break; // Got it!
}
// CAS to claim the key-slot failed.
//
// This re-read of the Key points out an annoying short-coming of Java
// CAS. Most hardware CAS's report back the existing value - so that
// if you fail you have a *witness* - the value which caused the CAS
// to fail. The Java API turns this into a boolean destroying the
// witness. Re-reading does not recover the witness because another
// thread can write over the memory after the CAS. Hence we can be in
// the unfortunate situation of having a CAS fail *for cause* but
// having that cause removed by a later store. This turns a
// non-spurious-failure CAS (such as Azul has) into one that can
// apparently spuriously fail - and we avoid apparent spurious failure
// by not allowing Keys to ever change.
K = key(kvs,idx); // CAS failed, get updated value
assert K != null; // If keys[idx] is null, CAS shoulda worked
}
// Key slot was not null, there exists a Key here
// We need a volatile-read here to preserve happens-before semantics on
// newly inserted Keys. If the Key body was written just before inserting
// into the table a Key-compare here might read the uninitalized Key body.
// Annoyingly this means we have to volatile-read before EACH key compare.
newkvs = chm._newkvs; // VOLATILE READ before key compare
if( keyeq(K,key,hashes,idx,fullhash) )
break; // Got it!
// get and put must have the same key lookup logic! Lest 'get' give
// up looking too soon.
//topmap._reprobes.add(1);
if( ++reprobe_cnt >= reprobe_limit(len) || // too many probes or
K == TOMBSTONE ) { // found a TOMBSTONE key, means no more keys
// We simply must have a new table to do a 'put'. At this point a
// 'get' will also go to the new table (if any). We do not need
// to claim a key slot (indeed, we cannot find a free one to claim!).
newkvs = chm.resize(topmap,kvs);
if( expVal != null ) topmap.help_copy(newkvs); // help along an existing copy
return putIfMatch(topmap,newkvs,key,putval,expVal);
}
idx = (idx+1)&(len-1); // Reprobe!
} // End of spinning till we get a Key slot
// ---
// Found the proper Key slot, now update the matching Value slot. We
// never put a null, so Value slots monotonically move from null to
// not-null (deleted Values use Tombstone). Thus if 'V' is null we
// fail this fast cutout and fall into the check for table-full.
if( putval == V ) return V; // Fast cutout for no-change
// See if we want to move to a new table (to avoid high average re-probe
// counts). We only check on the initial set of a Value from null to
// not-null (i.e., once per key-insert). Of course we got a 'free' check
// of newkvs once per key-compare (not really free, but paid-for by the
// time we get here).
if( newkvs == null && // New table-copy already spotted?
// Once per fresh key-insert check the hard way
((V == null && chm.tableFull(reprobe_cnt,len)) ||
// Or we found a Prime, but the JMM allowed reordering such that we
// did not spot the new table (very rare race here: the writing
// thread did a CAS of _newkvs then a store of a Prime. This thread
// reads the Prime, then reads _newkvs - but the read of Prime was so
// delayed (or the read of _newkvs was so accelerated) that they
// swapped and we still read a null _newkvs. The resize call below
// will do a CAS on _newkvs forcing the read.
V instanceof Prime) )
newkvs = chm.resize(topmap,kvs); // Force the new table copy to start
// See if we are moving to a new table.
// If so, copy our slot and retry in the new table.
if( newkvs != null )
return putIfMatch(topmap,chm.copy_slot_and_check(topmap,kvs,idx,expVal),key,putval,expVal);
// ---
// We are finally prepared to update the existing table
while( true ) {
assert !(V instanceof Prime);
// Must match old, and we do not? Then bail out now. Note that either V
// or expVal might be TOMBSTONE. Also V can be null, if we've never
// inserted a value before. expVal can be null if we are called from
// copy_slot.
if( expVal != NO_MATCH_OLD && // Do we care about expected-Value at all?
V != expVal && // No instant match already?
(expVal != MATCH_ANY || V == TOMBSTONE || V == null) &&
!(V==null && expVal == TOMBSTONE) && // Match on null/TOMBSTONE combo
(expVal == null || !expVal.equals(V)) ) // Expensive equals check at the last
return V; // Do not update!
// Actually change the Value in the Key,Value pair
if( CAS_val(kvs, idx, V, putval ) ) {
// CAS succeeded - we did the update!
// Both normal put's and table-copy calls putIfMatch, but table-copy
// does not (effectively) increase the number of live k/v pairs.
if( expVal != null ) {
// Adjust sizes - a striped counter
if( (V == null || V == TOMBSTONE) && putval != TOMBSTONE ) chm._size.add( 1);
if( !(V == null || V == TOMBSTONE) && putval == TOMBSTONE ) chm._size.add(-1);
}
return (V==null && expVal!=null) ? TOMBSTONE : V;
}
// Else CAS failed
V = val(kvs,idx); // Get new value
// If a Prime'd value got installed, we need to re-run the put on the
// new table. Otherwise we lost the CAS to another racing put.
// Simply retry from the start.
if( V instanceof Prime )
return putIfMatch(topmap,chm.copy_slot_and_check(topmap,kvs,idx,expVal),key,putval,expVal);
}
}
// --- help_copy ---------------------------------------------------------
// Help along an existing resize operation. This is just a fast cut-out
// wrapper, to encourage inlining for the fast no-copy-in-progress case. We
// always help the top-most table copy, even if there are nested table
// copies in progress.
private final Object[] help_copy( Object[] helper ) {
// Read the top-level KVS only once. We'll try to help this copy along,
// even if it gets promoted out from under us (i.e., the copy completes
// and another KVS becomes the top-level copy).
Object[] topkvs = _kvs;
CHM topchm = chm(topkvs);
if( topchm._newkvs == null ) return helper; // No copy in-progress
topchm.help_copy_impl(this,topkvs,false);
return helper;
}
// --- CHM -----------------------------------------------------------------
// The control structure for the NonBlockingHashMap
private static final class CHM<TypeK,TypeV> {
// Size in active K,V pairs
private final Counter _size;
public int size () { return (int)_size.get(); }
// ---
// These next 2 fields are used in the resizing heuristics, to judge when
// it is time to resize or copy the table. Slots is a count of used-up
// key slots, and when it nears a large fraction of the table we probably
// end up reprobing too much. Last-resize-milli is the time since the
// last resize; if we are running back-to-back resizes without growing
// (because there are only a few live keys but many slots full of dead
// keys) then we need a larger table to cut down on the churn.
// Count of used slots, to tell when table is full of dead unusable slots
private final Counter _slots;
public int slots() { return (int)_slots.get(); }
// ---
// New mappings, used during resizing.
// The 'new KVs' array - created during a resize operation. This
// represents the new table being copied from the old one. It's the
// volatile variable that is read as we cross from one table to the next,
// to get the required memory orderings. It monotonically transits from
// null to set (once).
volatile Object[] _newkvs;
private static final AtomicReferenceFieldUpdater<CHM,Object[]> _newkvsUpdater =
AtomicReferenceFieldUpdater.newUpdater(CHM.class,Object[].class, "_newkvs");
// Set the _next field if we can.
boolean CAS_newkvs( Object[] newkvs ) {
while( _newkvs == null )
if( _newkvsUpdater.compareAndSet(this,null,newkvs) )
return true;
return false;
}
// Sometimes many threads race to create a new very large table. Only 1
// wins the race, but the losers all allocate a junk large table with
// hefty allocation costs. Attempt to control the overkill here by
// throttling attempts to create a new table. I cannot really block here
// (lest I lose the non-blocking property) but late-arriving threads can
// give the initial resizing thread a little time to allocate the initial
// new table. The Right Long Term Fix here is to use array-lets and
// incrementally create the new very large array. In C I'd make the array
// with malloc (which would mmap under the hood) which would only eat
// virtual-address and not real memory - and after Somebody wins then we
// could in parallel initialize the array. Java does not allow
// un-initialized array creation (especially of ref arrays!).
volatile long _resizers; // count of threads attempting an initial resize
private static final AtomicLongFieldUpdater<CHM> _resizerUpdater =
AtomicLongFieldUpdater.newUpdater(CHM.class, "_resizers");
// ---
// Simple constructor
CHM( Counter size ) {
_size = size;
_slots= new Counter();
}
// --- tableFull ---------------------------------------------------------
// Heuristic to decide if this table is too full, and we should start a
// new table. Note that if a 'get' call has reprobed too many times and
// decided the table must be full, then always the estimate_sum must be
// high and we must report the table is full. If we do not, then we might
// end up deciding that the table is not full and inserting into the
// current table, while a 'get' has decided the same key cannot be in this
// table because of too many reprobes. The invariant is:
// slots.estimate_sum >= max_reprobe_cnt >= reprobe_limit(len)
private final boolean tableFull( int reprobe_cnt, int len ) {
return
// Do the cheap check first: we allow some number of reprobes always
reprobe_cnt >= REPROBE_LIMIT &&
// More expensive check: see if the table is > 1/4 full.
_slots.estimate_get() >= reprobe_limit(len);
}
// --- resize ------------------------------------------------------------
// Resizing after too many probes. "How Big???" heuristics are here.
// Callers will (not this routine) will 'help_copy' any in-progress copy.
// Since this routine has a fast cutout for copy-already-started, callers
// MUST 'help_copy' lest we have a path which forever runs through
// 'resize' only to discover a copy-in-progress which never progresses.
private final Object[] resize( NonBlockingHashMap topmap, Object[] kvs) {
assert chm(kvs) == this;
// Check for resize already in progress, probably triggered by another thread
Object[] newkvs = _newkvs; // VOLATILE READ
if( newkvs != null ) // See if resize is already in progress
return newkvs; // Use the new table already
// No copy in-progress, so start one. First up: compute new table size.
int oldlen = len(kvs); // Old count of K,V pairs allowed
int sz = size(); // Get current table count of active K,V pairs
int newsz = sz; // First size estimate
// Heuristic to determine new size. We expect plenty of dead-slots-with-keys
// and we need some decent padding to avoid endless reprobing.
if( sz >= (oldlen>>2) ) { // If we are >25% full of keys then...
newsz = oldlen<<1; // Double size
if( sz >= (oldlen>>1) ) // If we are >50% full of keys then...
newsz = oldlen<<2; // Double double size
}
// This heuristic in the next 2 lines leads to a much denser table
// with a higher reprobe rate
//if( sz >= (oldlen>>1) ) // If we are >50% full of keys then...
// newsz = oldlen<<1; // Double size
// Last (re)size operation was very recent? Then double again; slows
// down resize operations for tables subject to a high key churn rate.
long tm = System.currentTimeMillis();
long q=0;
if( newsz <= oldlen && // New table would shrink or hold steady?
tm <= topmap._last_resize_milli+10000 && // Recent resize (less than 1 sec ago)
(q=_slots.estimate_get()) >= (sz<<1) ) // 1/2 of keys are dead?
newsz = oldlen<<1; // Double the existing size
// Do not shrink, ever
if( newsz < oldlen ) newsz = oldlen;
// Convert to power-of-2
int log2;
for( log2=MIN_SIZE_LOG; (1<<log2) < newsz; log2++ ) ; // Compute log2 of size
// Now limit the number of threads actually allocating memory to a
// handful - lest we have 750 threads all trying to allocate a giant
// resized array.
long r = _resizers;
while( !_resizerUpdater.compareAndSet(this,r,r+1) )
r = _resizers;
// Size calculation: 2 words (K+V) per table entry, plus a handful. We
// guess at 32-bit pointers; 64-bit pointers screws up the size calc by
// 2x but does not screw up the heuristic very much.
int megs = ((((1<<log2)<<1)+4)<<3/*word to bytes*/)>>20/*megs*/;
if( r >= 2 && megs > 0 ) { // Already 2 guys trying; wait and see
newkvs = _newkvs; // Between dorking around, another thread did it
if( newkvs != null ) // See if resize is already in progress
return newkvs; // Use the new table already
// TODO - use a wait with timeout, so we'll wakeup as soon as the new table
// is ready, or after the timeout in any case.
//synchronized( this ) { wait(8*megs); } // Timeout - we always wakeup
// For now, sleep a tad and see if the 2 guys already trying to make
// the table actually get around to making it happen.
try { Thread.sleep(8*megs); } catch( Exception e ) { }
}
// Last check, since the 'new' below is expensive and there is a chance
// that another thread slipped in a new thread while we ran the heuristic.
newkvs = _newkvs;
if( newkvs != null ) // See if resize is already in progress
return newkvs; // Use the new table already
// Double size for K,V pairs, add 1 for CHM
newkvs = new Object[((1<<log2)<<1)+2]; // This can get expensive for big arrays
newkvs[0] = new CHM(_size); // CHM in slot 0
newkvs[1] = new int[1<<log2]; // hashes in slot 1
// Another check after the slow allocation
if( _newkvs != null ) // See if resize is already in progress
return _newkvs; // Use the new table already
// The new table must be CAS'd in so only 1 winner amongst duplicate
// racing resizing threads. Extra CHM's will be GC'd.
if( CAS_newkvs( newkvs ) ) { // NOW a resize-is-in-progress!
//notifyAll(); // Wake up any sleepers
//long nano = System.nanoTime();
//System.out.println(" "+nano+" Resize from "+oldlen+" to "+(1<<log2)+" and had "+(_resizers-1)+" extras" );
//if( System.out != null ) System.out.print("["+log2);
topmap.rehash(); // Call for Hashtable's benefit
} else // CAS failed?
newkvs = _newkvs; // Reread new table
return newkvs;
}
// The next part of the table to copy. It monotonically transits from zero
// to _kvs.length. Visitors to the table can claim 'work chunks' by
// CAS'ing this field up, then copying the indicated indices from the old
// table to the new table. Workers are not required to finish any chunk;
// the counter simply wraps and work is copied duplicately until somebody
// somewhere completes the count.
volatile long _copyIdx = 0;
static private final AtomicLongFieldUpdater<CHM> _copyIdxUpdater =
AtomicLongFieldUpdater.newUpdater(CHM.class, "_copyIdx");
// Work-done reporting. Used to efficiently signal when we can move to
// the new table. From 0 to len(oldkvs) refers to copying from the old
// table to the new.
volatile long _copyDone= 0;
static private final AtomicLongFieldUpdater<CHM> _copyDoneUpdater =
AtomicLongFieldUpdater.newUpdater(CHM.class, "_copyDone");
// --- help_copy_impl ----------------------------------------------------
// Help along an existing resize operation. We hope its the top-level
// copy (it was when we started) but this CHM might have been promoted out
// of the top position.
private final void help_copy_impl( NonBlockingHashMap topmap, Object[] oldkvs, boolean copy_all ) {
assert chm(oldkvs) == this;
Object[] newkvs = _newkvs;
assert newkvs != null; // Already checked by caller
int oldlen = len(oldkvs); // Total amount to copy
final int MIN_COPY_WORK = Math.min(oldlen,1024); // Limit per-thread work
// ---
int panic_start = -1;
int copyidx=-9999; // Fool javac to think it's initialized
while( _copyDone < oldlen ) { // Still needing to copy?
// Carve out a chunk of work. The counter wraps around so every
// thread eventually tries to copy every slot repeatedly.
// We "panic" if we have tried TWICE to copy every slot - and it still
// has not happened. i.e., twice some thread somewhere claimed they
// would copy 'slot X' (by bumping _copyIdx) but they never claimed to
// have finished (by bumping _copyDone). Our choices become limited:
// we can wait for the work-claimers to finish (and become a blocking
// algorithm) or do the copy work ourselves. Tiny tables with huge
// thread counts trying to copy the table often 'panic'.
if( panic_start == -1 ) { // No panic?
copyidx = (int)_copyIdx;
while( copyidx < (oldlen<<1) && // 'panic' check
!_copyIdxUpdater.compareAndSet(this,copyidx,copyidx+MIN_COPY_WORK) )
copyidx = (int)_copyIdx; // Re-read
if( !(copyidx < (oldlen<<1)) ) // Panic!
panic_start = copyidx; // Record where we started to panic-copy
}
// We now know what to copy. Try to copy.
int workdone = 0;
for( int i=0; i<MIN_COPY_WORK; i++ )
if( copy_slot(topmap,(copyidx+i)&(oldlen-1),oldkvs,newkvs) ) // Made an oldtable slot go dead?
workdone++; // Yes!
if( workdone > 0 ) // Report work-done occasionally
copy_check_and_promote( topmap, oldkvs, workdone );// See if we can promote
//for( int i=0; i<MIN_COPY_WORK; i++ )
// if( copy_slot(topmap,(copyidx+i)&(oldlen-1),oldkvs,newkvs) ) // Made an oldtable slot go dead?
// copy_check_and_promote( topmap, oldkvs, 1 );// See if we can promote
copyidx += MIN_COPY_WORK;
// Uncomment these next 2 lines to turn on incremental table-copy.
// Otherwise this thread continues to copy until it is all done.
if( !copy_all && panic_start == -1 ) // No panic?
return; // Then done copying after doing MIN_COPY_WORK
}
// Extra promotion check, in case another thread finished all copying
// then got stalled before promoting.
copy_check_and_promote( topmap, oldkvs, 0 );// See if we can promote
}
// --- copy_slot_and_check -----------------------------------------------
// Copy slot 'idx' from the old table to the new table. If this thread
// confirmed the copy, update the counters and check for promotion.
//
// Returns the result of reading the volatile _newkvs, mostly as a
// convenience to callers. We come here with 1-shot copy requests
// typically because the caller has found a Prime, and has not yet read
// the _newkvs volatile - which must have changed from null-to-not-null
// before any Prime appears. So the caller needs to read the _newkvs
// field to retry his operation in the new table, but probably has not
// read it yet.
private final Object[] copy_slot_and_check( NonBlockingHashMap topmap, Object[] oldkvs, int idx, Object should_help ) {
assert chm(oldkvs) == this;
Object[] newkvs = _newkvs; // VOLATILE READ
// We're only here because the caller saw a Prime, which implies a
// table-copy is in progress.
assert newkvs != null;
if( copy_slot(topmap,idx,oldkvs,_newkvs) ) // Copy the desired slot
copy_check_and_promote(topmap, oldkvs, 1); // Record the slot copied
// Generically help along any copy (except if called recursively from a helper)
return (should_help == null) ? newkvs : topmap.help_copy(newkvs);
}
// --- copy_check_and_promote --------------------------------------------
private final void copy_check_and_promote( NonBlockingHashMap topmap, Object[] oldkvs, int workdone ) {
assert chm(oldkvs) == this;
int oldlen = len(oldkvs);
// We made a slot unusable and so did some of the needed copy work
long copyDone = _copyDone;
assert (copyDone+workdone) <= oldlen;
if( workdone > 0 ) {
while( !_copyDoneUpdater.compareAndSet(this,copyDone,copyDone+workdone) ) {
copyDone = _copyDone; // Reload, retry
assert (copyDone+workdone) <= oldlen;
}
//if( (10*copyDone/oldlen) != (10*(copyDone+workdone)/oldlen) )
//System.out.print(" "+(copyDone+workdone)*100/oldlen+"%"+"_"+(_copyIdx*100/oldlen)+"%");
}
// Check for copy being ALL done, and promote. Note that we might have
// nested in-progress copies and manage to finish a nested copy before
// finishing the top-level copy. We only promote top-level copies.