Cocurrenthashmap effectHashtable by locking the whole table to achieve concurrent hash lookup and storage, COCURRENTHASHMAPT through segment way can achieve the same function, but more efficient, At the time of jdk1.6, Cocuenthashmap had a weak consistency problem, but in jdk1.7, the problem was fixed. So concurrency security or performance is very high. Next I try to analyze cocurrenthashmap based on the source code of jdk1.7.
Cocurrenthashmap Initialization preprocessing
//unsafe mechanics private static final sun.misc.Unsafe unsafe; Private static final long sbase; private static final int sshift; Private static final long tbase; private static final int tshift; static {int SS, TS; try {UNSAFE = Sun.misc.Unsafe.getUnsafe (); Class<?> TC = Hashentry[].class; class<?> sc = segment[].class; Tbase = Unsafe.arraybaseoffset (TC); Sbase = Unsafe.arraybaseoffset (SC); TS = Unsafe.arrayindexscale (TC); SS = Unsafe.arrayindexscale (SC); } catch (Exception e) {throw new Error (e); } if ((SS & (ss-1))! = 0 | | (TS & (ts-1))! = 0) throw new Error ("Data type scale not a power of"); Sshift = 31-integer.numberofleadingzeros (ss); Tshift = 31-integer.numberofleadingzeros (ts); }
Code parsing: First get the unsafe to provide CAS operations, Java bottom-line multithreading is done through the CAS, but the CAS operation for high-precision concurrency is still a certain problem. "As for this question, analyze it later." Both Unsafe.arraybaseoffset (TC) and Unsafe.arraybaseoffset (SC) are used to calculate the memory offset values of Hashentry and segment entity objects relative to the array object. This is the value that the CAS operation must get.notes:
Gets the offset (get offset of a first element in the array) of the initial elements in a native int arraybaseoffset (Java.lang.Class aclass) ; Gets the size of an element within the array (get size of an element in the array)
Cocurrenthashmap Initialization
Public Concurrenthashmap (int initialcapacity, float loadfactor, int concurrencylevel) { if (! ( Loadfactor > 0) | | Initialcapacity < 0 | | Concurrencylevel <= 0) throw new IllegalArgumentException (); if (Concurrencylevel > max_segments) concurrencylevel = max_segments; Find Power-of-two sizes Best matching arguments int sshift = 0; int ssize = 1; while (Ssize < concurrencylevel) {++sshift; Ssize <<= 1; } this.segmentshift = 32-sshift; This.segmentmask = ssize-1; if (initialcapacity > maximum_capacity) initialcapacity = maximum_capacity; int c = initialcapacity/ssize; if (c * ssize < initialcapacity) ++c; int cap = min_segment_table_capacity; while (Cap < c) Cap <<= 1; Create segments and segments[0] segment<k,v> S0 = New Segment<k,v> (Loadfactor, (int) (CAP * loadfactor), (hashentry<k,v>[] ) New hashentry<?,? >[cap]); segment<k,v>[] ss = (segment<k,v>[]) new segment<?,? >[ssize]; Unsafe.putorderedobject (SS, Sbase, S0); Ordered write of segments[0] this.segments = SS; }
Code parsing: The main function of the above code is to initialize the segment[] Array object and the Segment object. Parse to analyze the most important put and get methods.
Put method
<span style= "FONT-SIZE:18PX;" > Public v put (K key, V value) { segment<k,v> s; if (value = = null) throw new NullPointerException (); int hash = hash (Key.hashcode ()); Int J = (hash >>> segmentshift) & Segmentmask; if ((s = (segment<k,v>) unsafe.getobject //nonvolatile; recheck (segments, (J << Sshift) + sbase) = = NULL)//in ensuresegment s = ensuresegment (j); Return S.put (key, hash, value, false); } </span>
Code parsing: The general meaning is to calculate the hash value of key first, and then use this hash value to get the segment object. The segment object then executes the Put method. This completes the put operation. Because this process is very important, we definitely want to know how it handles concurrency and internal implementations.
ensuresegment
Private segment<k,v> ensuresegment (int K) {Final segment<k,v>[] ss = this.segments; Long u = (k << sshift) + sbase; Raw offset segment<k,v> seg; if (seg = (segment<k,v>) unsafe.getobjectvolatile (ss, u)) = = null) {segment<k,v> proto = Ss[0];// Use segment 0 as prototype int cap = Proto.table.length; float lf = proto.loadfactor; int threshold = (int) (CAP * lf); hashentry<k,v>[] tab = (hashentry<k,v>[]) new hashentry<?,? >[cap]; if (seg = (segment<k,v>) unsafe.getobjectvolatile (ss, u)) = = null) {//Recheck Segmen t<k,v> s = new segment<k,v> (LF, threshold, TAB); while (seg = (segment<k,v>) unsafe.getobjectvolatile (ss, u)) = = null) {if (Unsafe.compareandswapobject (SS, U, NULL, SEG = s)) break; }}} return seg; }
The offset is calculated first, and then the unsafe is used to get the object. Here it is possible to get a bit confused about this offset value, and here I also analyze this offset to get both long u= (K<<sshift) +sbase; It is possible for everyone to ask why this is calculated. The average person calculates the offset value: set K to index,size as the size of the object, _offset to the offset value of the first element, and the offset value should be offset=index*size+offset. Yes, theoretically, that's right. In the Java memory model, however, memory follows 8-byte alignment. So in the Java memory model you are wrong to calculate this way. There are previous initializations to know: Sshift the number of bits corresponding to the binary number of the object size. So the K<<sshift also achieves 8-byte alignment.
Segment->put
Final V put (K key, int hash, V value, Boolean onlyifabsent) {hashentry<k,v> node = Trylock ()? n Ull:scanandlockforput (key, hash, value); V OldValue; try {hashentry<k,v>[] tab = table; int index = (tab.length-1) & hash; hashentry<k,v> first = Entryat (tab, index); for (hashentry<k,v> e = first;;) {if (E! = null) {k k; if (k = e.key) = = Key | | (E.hash = = Hash && key.equals (k))) {oldValue = E.value; if (!onlyifabsent) {e.value = value; ++modcount; } break; } e = E.next; } else { if (node! = null) Node.setnext (first); else node = new Hashentry<k,v> (hash, key, value, first); int c = count + 1; if (C > Threshold && tab.length < maximum_capacity) rehash (node); else Setentryat (tab, Index, node); ++modcount; Count = C; OldValue = null; Break }}} finally {unlock (); } return oldValue; }Here's a flowchart:
get ()
Public V get (Object key) { segment<k,v> S;//Manually integrate access methods to reduce overhead hashentry <k,v>[] tab; int h = hash (Key.hashcode ()); Long U = (((H >>> segmentshift) & Segmentmask) << sshift) + sbase; if ((s = (segment<k,v>) unsafe.getobjectvolatile (segments, u))! = null && (tab = s.table)! = null) { for (hashentry<k,v> e = (hashentry<k,v>) unsafe.getobjectvolatile (tab, ((Long) ((tab.length-1) & h)) << Tshift) + tbase); E! = null; E = E.next) { K K; if (k = e.key) = = Key | | (E.hash = = h && key.equals (k))) return e.value; } } return null; }
The principle is simple to first locate the segment and then navigate to the entity. And the Getobjectvolatie guarantees the ability to read the latest data.
Summary: Concurrenthashmap implementation involves a lot of multi-threaded knowledge and Java memory model this knowledge, if not enough ability, mind not to imitate, but we can learn its ideas and how to achieve.
Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.
Concurrenthashmap Source Code Analysis