日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java并发编程之并发容器ConcurrentHashMap(JDK1.7)解析

發(fā)布時(shí)間:2025/3/11 java 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java并发编程之并发容器ConcurrentHashMap(JDK1.7)解析 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

最近看了一下ConcurrentHashMap的相關(guān)代碼,感覺(jué)JDK1.7和JDK1.8差別挺大的,這次先看下JDK1.7是怎么實(shí)現(xiàn)的吧

哈希(hash)

先了解一下啥是哈希(網(wǎng)上有很多介紹),是一種散列函數(shù),簡(jiǎn)單來(lái)說(shuō)就是將輸入值轉(zhuǎn)換為固定值的一種壓縮映射,在Java中最常見(jiàn)的就是Object.hashCode(),通過(guò)固定算法計(jì)算出來(lái)的一個(gè)值

數(shù)據(jù)結(jié)構(gòu)

ConcurrentHashMap主要結(jié)構(gòu)是有Segment<K,V>以及HashEntry<K,V>鏈表組成的

我們先看一下HashEntry<K,V>的主要結(jié)構(gòu),還是單向鏈表的數(shù)據(jù)結(jié)構(gòu):

static final class HashEntry<K,V> {final int hash;//hash值final K key;//存儲(chǔ)keyvolatile V value;//存儲(chǔ)值volatile HashEntry<K,V> next;//指向下一個(gè),單向鏈表HashEntry(int hash, K key, V value, HashEntry<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}//......}

?再來(lái)看一下Segment<K,V>的數(shù)據(jù)結(jié)構(gòu),主要還是用到了HashEntry<K,V>數(shù)組:

static final class Segment<K,V> extends ReentrantLock implements Serializable {//數(shù)據(jù)儲(chǔ)存數(shù)組transient volatile HashEntry<K,V>[] table;/*** The load factor for the hash table. Even though this value* is same for all segments, it is replicated to avoid needing* links to outer object.* @serial*///擴(kuò)容因子,當(dāng)Segment的數(shù)量大于initialCapacity* loadFactor就會(huì)擴(kuò)容final float loadFactor;/*** The table is rehashed when its size exceeds this threshold.* (The value of this field is always <tt>(int)(capacity ** loadFactor)</tt>.)*///閾值,超出后就必須重新散列,就是擴(kuò)容transient int threshold;Segment(float lf, int threshold, HashEntry<K,V>[] tab) {this.loadFactor = lf;this.threshold = threshold;this.table = tab;}//..... }

接下來(lái)看一下ConcurrentHashMap的構(gòu)造函數(shù)以及相關(guān)變量:

/*** The default initial capacity for this table,* used when not otherwise specified in a constructor.*///容器的默認(rèn)大小static final int DEFAULT_INITIAL_CAPACITY = 16;/*** The default load factor for this table, used when not* otherwise specified in a constructor.*///用來(lái)調(diào)整大小的,就是擴(kuò)容static final float DEFAULT_LOAD_FACTOR = 0.75f;/*** The default concurrency level for this table, used when not* otherwise specified in a constructor.*///并發(fā)時(shí)訪問(wèn)的線(xiàn)程數(shù)量static final int DEFAULT_CONCURRENCY_LEVEL = 16; final Segment<K,V>[] segments;//數(shù)據(jù)存儲(chǔ)的數(shù)組//最大并發(fā)的線(xiàn)程數(shù),不能超過(guò)65536static final int MAX_SEGMENTS = 1 << 16; // slightly conservative//最大容量數(shù),不能超過(guò)2的30次方static final int MAXIMUM_CAPACITY = 1 << 30;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 argumentsint 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;}

在構(gòu)造方法中可以看到,其實(shí)還是創(chuàng)建一個(gè)Segment的數(shù)組,默認(rèn)的話(huà)長(zhǎng)度為16,并且將s0變量賦值進(jìn)去,s0中的HashEntry數(shù)組的大小默認(rèn)為2。

接下來(lái)看一下我們經(jīng)常用put()方法,源代碼如下:

首先需要計(jì)算key值的hash值,計(jì)算方法是固定的算法,然后判斷Segment數(shù)組中是否有這個(gè)hash值的數(shù)據(jù),如果不存在的話(huà),則進(jìn)入擴(kuò)容方法ensureSegment(j);在這個(gè)方法中可以看到擴(kuò)容新數(shù)組的長(zhǎng)度為table.length *?loadFactor,即每次擴(kuò)容為initialCapacity* loadFactor,只會(huì)擴(kuò)容HashEntry數(shù)組,并非Segment數(shù)組;如果存在的話(huà),則調(diào)用Segment的put()方法,這個(gè)方法總共有四個(gè)參數(shù),最后一個(gè)參數(shù)是用于區(qū)別putIfAbsent()以及put(),這兩個(gè)方法區(qū)別簡(jiǎn)單來(lái)說(shuō)就是,判斷當(dāng)前key存不存在,如果存在的話(huà)put()方法就是覆蓋,而putIfAbsent()就是不覆蓋,并且這兩個(gè)方法都會(huì)返回舊值,在下面的有Segment的put方法解析。

@SuppressWarnings("unchecked")public V put(K key, V value) {Segment<K,V> s;if (value == null)throw new NullPointerException();int hash = hash(key);int j = (hash >>> segmentShift) & segmentMask;if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegments = ensureSegment(j);return s.put(key, hash, value, false);}private int hash(Object k) {int h = hashSeed;if ((0 != h) && (k instanceof String)) {return sun.misc.Hashing.stringHash32((String) k);}h ^= k.hashCode();// 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);}//擴(kuò)容Segment的數(shù)組,private Segment<K,V> ensureSegment(int k) {final Segment<K,V>[] ss = this.segments;long u = (k << SSHIFT) + SBASE; // raw offsetSegment<K,V> seg;if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {Segment<K,V> proto = ss[0]; // use segment 0 as prototypeint 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) { // recheckSegment<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;}//Segement中的put方法:可以看到,首先會(huì)先去獲取鎖final V put(K key, int hash, V value, boolean onlyIfAbsent) {HashEntry<K,V> node = tryLock() ? null :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;;) {//循環(huán)鏈表上的節(jié)點(diǎn)判斷if (e != null) {K k;if ((k = e.key) == key ||(e.hash == hash && key.equals(k))) {oldValue = e.value;//返回舊值if (!onlyIfAbsent) {e.value = value;//如果是putIfAbsent()則不執(zhí)行這段覆蓋代碼++modCount;}break;}e = e.next;//鏈表的下一個(gè)節(jié)點(diǎn)}else {//如果在對(duì)應(yīng)的table數(shù)組中不存在則創(chuàng)建一個(gè)HashEntry節(jié)點(diǎn),或者創(chuàng)建一個(gè)if (node != null)node.setNext(first);elsenode = new HashEntry<K,V>(hash, key, value, first);int c = count + 1;if (c > threshold && tab.length < MAXIMUM_CAPACITY)rehash(node);elsesetEntryAt(tab, index, node);++modCount;count = c;oldValue = null;break;}}} finally {unlock();//釋放鎖}return oldValue;

接下來(lái)看看get()方法,其實(shí)get()方法的現(xiàn)對(duì)來(lái)說(shuō)較為簡(jiǎn)單,在定位segment和定位table后,依次掃描這個(gè)table元素下的的鏈表,要么找到元素,要么返回null。這里可能會(huì)有個(gè)并發(fā)問(wèn)題如何獲取是最新的,因?yàn)樵贖ashEntry設(shè)計(jì)當(dāng)中value屬性的使用了 volatile保證了數(shù)據(jù)的可見(jiàn)性。但是在獲取的時(shí)候并未上鎖,所以在使用get()以及containsKey()方法會(huì)存在一致性問(wèn)題,由于HashEntry是鏈表結(jié)構(gòu),所以在并發(fā)情況下如果其他線(xiàn)程進(jìn)行修改HashEntry鏈表值的話(huà)(即會(huì)修改鏈表結(jié)構(gòu),導(dǎo)致鏈表的next節(jié)點(diǎn)地址錯(cuò)亂),返回值并非是實(shí)時(shí)數(shù)據(jù)。

public V get(Object key) {Segment<K,V> s; // manually integrate access methods to reduce overheadHashEntry<K,V>[] tab;int h = hash(key);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;}//獲取containsKey的值public boolean containsKey(Object key) {Segment<K,V> s; // same as get() except no need for volatile value readHashEntry<K,V>[] tab;int h = hash(key);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 true;}}return false;} //所以在使用key為Object的時(shí)候需要重寫(xiě)一下equals以及hashCode方法

在使用size()時(shí)候,會(huì)進(jìn)去兩次統(tǒng)計(jì),并且不是加鎖統(tǒng)計(jì),兩次一致直接返回結(jié)果,不一致,重新加鎖再次統(tǒng)計(jì)

public int size() {// Try a few times to get accurate count. On failure due to// continuous async changes in table, resort to locking.final Segment<K,V>[] segments = this.segments;int size;boolean overflow; // true if size overflows 32 bitslong sum; // sum of modCountslong last = 0L; // previous sumint retries = -1; // first iteration isn't retrytry {for (;;) {//第一次統(tǒng)計(jì)if (retries++ == RETRIES_BEFORE_LOCK) {for (int j = 0; j < segments.length; ++j)ensureSegment(j).lock(); // force creation}sum = 0L;size = 0;overflow = false;//第二次統(tǒng)計(jì)for (int j = 0; j < segments.length; ++j) {Segment<K,V> seg = segmentAt(segments, j);if (seg != null) {sum += seg.modCount;int c = seg.count;if (c < 0 || (size += c) < 0)overflow = true;}}if (sum == last)break;last = sum;}} finally {if (retries > RETRIES_BEFORE_LOCK) {for (int j = 0; j < segments.length; ++j)segmentAt(segments, j).unlock();}}return overflow ? Integer.MAX_VALUE : size;}

其他方法我就不介紹啦,下次再看一點(diǎn)JDK1.8的ConcurrentHashMap源代碼,寫(xiě)的不是很好,不要見(jiàn)怪咯

總結(jié)

以上是生活随笔為你收集整理的Java并发编程之并发容器ConcurrentHashMap(JDK1.7)解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。