併發集合在分析之CurrentHashMap之從應用去分析,分段加鎖應用
實際應用:
Java程式碼- ConcurrentMap<String, String> map = new ConcurrentHashMap<String, String>();
- String oldValue = map.put("zhxing", "value");
- String oldValue1 = map.put("zhxing", "value1");
- String oldValue2 = map.putIfAbsent("zhxing", "value2");
- String value = map.get("zhxing");
- System.out.println("oldValue:" + oldValue);
- System.out.println("oldValue1:" + oldValue1);
- System.out.println("oldValue2:" + oldValue2);
- System.out.println("value:" + value);
輸出結果:
Java程式碼- oldValue:null
- oldValue1:value
- oldValue2:value1
- value:value1
先從new 方法開始
Java程式碼- /**
- * Creates a new, empty map with a default initial capacity (16), load
- * factor (0.75) and concurrencyLevel(也就是鎖的個數) (16).
- *
- */
- public ConcurrentHashMap() {
- this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
- }
- // 當都是預設的設定引數
- public ConcurrentHashMap(int initialCapacity, float loadFactor,
- int concurrencyLevel) {
- if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
- thrownew IllegalArgumentException();
- // MAX_SEGMENTS = 1 << 16,鎖的個數有限制
- 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;// 不斷右移
- }
- // 到這裡,sshift=4,ssize=16.因為concurrencyLevel=16=1<<4
- segmentShift = 32 - sshift;// =16
- segmentMask = ssize - 1;// =3
- // 建立了16個分段(Segment),其實每個分段相當於一個帶鎖的map
- this.segments = Segment.newArray(ssize);
- if (initialCapacity > MAXIMUM_CAPACITY)
- initialCapacity = MAXIMUM_CAPACITY;
- // 這裡是計算每個分段儲存的容量
- int c = initialCapacity / ssize;// c=16/16=1
- if (c * ssize < initialCapacity)// 防止分段的相加的容量小於總容量
- ++c;
- int cap = 1;
- // 如果初始容量比cap的容量小,則已雙倍的容量增加
- while (cap < c)
- cap <<= 1;
- // 分別new分段
- for (int i = 0; i < this.segments.length; ++i)
- this.segments[i] = new Segment<K, V>(cap, loadFactor);
- }
這裡提到了一個Segment 這個類,其實這個是總map 的分段,就是為了實現分段鎖機制。
Java程式碼- /**
- * Segments are specialized versions of hash tables. This subclasses from
- * ReentrantLock opportunistically, just to simplify some locking and avoid
- * separate construction. map 的分段實現,擴充套件了鎖機制
- */
- staticfinalclass Segment<K, V> extends ReentrantLock implements
- Serializable {
- //。。。
- Segment(int initialCapacity, float lf) {
- loadFactor = lf;
- // 這個是開始初始化map容器了
- setTable(HashEntry.<K, V> newArray(initialCapacity));
- }
- /**
- * Sets table to new HashEntry array. Call only while holding lock or in
- * constructor.
- */
- void setTable(HashEntry<K, V>[] newTable) {
- threshold = (int) (newTable.length * loadFactor);
- table = newTable;
- }
- }
- // 這個是實際儲存到map的東西了,如果對HashMap原始碼有了解的話,是不是覺得很像Hash.Entry,但又沒實現Map.Entry介面,它是用另外個類WriteThroughEntry
- // 來實現這個Map.Entry介面的。
- staticfinalclass HashEntry<K, V> {
- final K key;
- finalint hash;
- volatile V value;
- final HashEntry<K, V> next;
- HashEntry(K key, int hash, HashEntry<K, V> next, V value) {
- this.key = key;
- this.hash = hash;
- this.next = next;
- this.value = value;
- }
- @SuppressWarnings("unchecked")
- // 新建陣列,儲存著map裡的鍵值對
- staticfinal <K, V> HashEntry<K, V>[] newArray(int i) {
- returnnew HashEntry[i];
- }
get方法實現
Java程式碼- //ConcurrentHashMap類
- // 在這裡發現,get操作幾乎是不帶鎖的。。效率提高很多
- public V get(Object key) {
- // key不能為null 。。
- int hash = hash(key); // throws NullPointerException if key null
- return segmentFor(hash).get(key, hash);
- }
- // 這個hash方式不太懂,估計是為了能均勻分佈吧
- staticint hash(Object x) {
- int h = x.hashCode();
- h += ~(h << 9);
- h ^= (h >>> 14);
- h += (h << 4);
- h ^= (h >>> 10);
- return h;
- }
- /**
- * Returns the segment that should be used for key with given hash 這個是尋找所在分段
- *
- * @param hash
- * the hash code for the key
- * @return the segment
- */
- final Segment<K, V> segmentFor(int hash) {
- // hash>>>16&3
- return segments[(hash >>> segmentShift) & segmentMask];
- }
- //Segment 類方法
- /* Specialized implementations of map methods */
- // 獲得值了,和其他map的get的實現其實差不多
- V get(Object key, int hash) {
- // count 是每個分段的鍵值對個數,而且是volatile,保證在記憶體中只有一份
- if (count != 0) { // read-volatile
- // 獲得分段中hash連結串列的第一個值
- HashEntry<K, V> e = getFirst(hash);
- while (e != null) {
- if (e.hash == hash && key.equals(e.key)) {
- V v = e.value;
- if (v != null)
- return v;
- // 這個做了一個挺有趣的檢查,如果v==null,而key!=null,的時候會等待鎖中value的值
- return readValueUnderLock(e); // recheck
- }
- e = e.next;
- }
- }
- returnnull;
- }
- /**
- * Reads value field of an entry under lock. Called if value field ever
- * appears to be null. This is possible only if a compiler happens to
- * reorder a HashEntry initialization with its table assignment, which
- * is legal under memory model but is not known to ever occur.
- */
- V readValueUnderLock(HashEntry<K, V> e) {
- lock();
- try {
- return e.value;
- } finally {
- unlock();
- }
- }
put 方法
Java程式碼- //ConcurrentHashMap類
- // 注意的是key 和value 都不能為空
- public V put(K key, V value) {
- if (value == null)
- thrownew NullPointerException();
- // 和get方式一樣的hash 方式
- int hash = hash(key);
- return segmentFor(hash).put(key, hash, value, false);
- }
- //Segment 類
- V put(K key, int hash, V value, boolean onlyIfAbsent) {
- // 這裡加鎖了
- lock();
- try {
- int c = count;
- // 如果超過限制,就重新分配
- if (c++ > threshold) // ensure capacity
- rehash();
- HashEntry<K, V>[] tab = table;
- int index = hash & (tab.length - 1);
- HashEntry<K, V> first = tab[index];
- HashEntry<K, V> e = first;
- // e的值總是在連結串列的最後一個
- while (e != null && (e.hash != hash || !key.equals(e.key)))
- e = e.next;
- V oldValue;
- if (e != null) {
- oldValue = e.value;
- // 這裡就是實現putIfAbsent 的方式
- if (!onlyIfAbsent)
- e.value = value;
- } else {
- oldValue = null;
- ++modCount;
- tab[index] = new HashEntry<K, V>(key, hash, first, value);
- count = c; // write-volatile
- }
- return oldValue;
- } finally {
- unlock();
- }
- }
- // 這中擴容方式應該和其他map 的擴容一樣
- void rehash() {
- HashEntry<K, V>[] oldTable = table;
- int oldCapacity = oldTable.length;
- // 如果到了最大容量則不能再擴容了,max=1<<30,這將可能導致的一個後果是map的操作越來越慢
- if (oldCapacity >= MAXIMUM_CAPACITY)
- return;
- /*
- * Reclassify nodes in each list to new Map. Because we are using
- * power-of-two expansion, the elements from each bin must either
- * stay at same index, or move with a power of two offset. We
- * eliminate unnecessary node creation by catching cases where old
- * nodes can be reused because their next fields won't change.
- * Statistically, at the default threshold, only about one-sixth of
- * them need cloning when a table doubles. The nodes they replace
- * will be garbage collectable as soon as they are no longer
- * referenced by any reader thread that may be in the midst of
- * traversing table right now.
- */
- // 以兩倍的方式增長
- HashEntry<K, V>[] newTable = HashEntry.newArray(oldCapacity << 1);
- threshold = (int) (newTable.length * loadFactor);
- int sizeMask = newTable.length - 1;
- // 下面的資料拷貝就沒多少好講的了
- for (int i = 0; i < oldCapacity; i++) {
- // We need to guarantee that any existing reads of old Map can
- // proceed. So we cannot yet null out each bin.
- HashEntry<K, V> e = oldTable[i];
- if (e != null) {
- HashEntry<K, V> next = e.next;
- int idx = e.hash & sizeMask;
- // Single node on list
- if (next == null)
- newTable[idx] = e;
- else {
- // Reuse trailing consecutive sequence at same slot
- HashEntry<K, V> lastRun = e;
- int lastIdx = idx;
- for (HashEntry<K, V> last = next; last != null; last = last.next) {
- int k = last.hash & sizeMask;
- if (k != lastIdx) {
- lastIdx = k;
- lastRun = last;
- }
- }
- newTable[lastIdx] = lastRun;
- // Clone all remaining nodes
- for (HashEntry<K, V> p = e; p != lastRun; p = p.next) {
- int k = p.hash & sizeMask;
- HashEntry<K, V> n = newTable[k];
- newTable[k] = new HashEntry<K, V>(p.key, p.hash, n,
- p.value);
- }
- }
- }
- }
- table = newTable;
- }
size 方法
Java程式碼- /**
- * Returns the number of key-value mappings in this map. If the map contains
- * more than <tt>Integer.MAX_VALUE</tt> elements, returns
- * <tt>Integer.MAX_VALUE</tt>. javadoc 上也寫明瞭,返回的數值不能超過Int的最大值,超過也返回最大值
- * 在下面的分析也可以看出,為了減少鎖競爭做了一些效能優化,這種的優化方式在很多方法都有使用
- *
- * @return the number of key-value mappings in this map
- */
- publicint size() {
- final Segment<K, V>[] segments = this.segments;
- long sum = 0;
- long check = 0;
- int[] mc = newint[segments.length];
- // Try a few times to get accurate count. On failure due to
- // continuous async changes in table, resort to locking.
- // 這裡最多試RETRIES_BEFORE_LOCK=2 次的檢查對比
- for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
- check = 0;
- sum = 0;// size 總數
- int mcsum = 0;// 修改的總次數
- // 這裡儲存了一份對比值,供下次對比時使用
- for (int i = 0; i < segments.length; ++i) {
- sum += segments[i].count;
- mcsum += mc[i] = segments[i].modCount;
- }
- // 只有當map初始化的時候才等於0
- if (mcsum != 0) {
- // 在此對比上面儲存的修改值
- for (int i = 0; i < segments.length; ++i) {
- check += segments[i].count;
- if (mc[i] != segments[i].modCount) {
- check = -1; // force retry
- break;
- }
- }
- }
- // 檢查和第一次儲存值一樣則結束迴圈
- if (check == sum)
- break;
- }
- // 當不相等的時候,這裡就只有用鎖來保證正確性了
- if (check != sum) { // Resort to locking all segments
- sum = 0;
- for (int i = 0; i < segments.length; ++i)
- segments[i].lock();
- for (int i = 0; i < segments.length; ++i)
- sum += segments[i].count;
- for (int i = 0; i < segments.length; ++i)
- segments[i].unlock();
- }
- // 這裡也可以看出,如果超過int 的最大值值返回int 最大值
- if (sum > Integer.MAX_VALUE)
- return Integer.MAX_VALUE;
- else
- return (int) sum;
- }
keys 方法
Java程式碼- public Enumeration<K> keys() {
- //這裡新建了一個內部Iteraotr 類
- returnnew KeyIterator();
- }
- //這裡主要是繼承了HashIterator 方法,基本的實現都在HashIterator 中
- finalclass KeyIterator extends HashIterator implements Iterator<K>,
- Enumeration<K> {
- public K next() {
- returnsuper.nextEntry().key;
- }
- public K nextElement() {
- returnsuper.nextEntry().key;
- }
- }
- /* ---------------- Iterator Support -------------- */
- // 分析程式碼發現,這個遍歷過程沒有涉及到鎖,檢視Javadoc 後可知該檢視的 iterator 是一個“弱一致”的迭代器。。
- abstractclass HashIterator {
- int nextSegmentIndex;// 下一個分段的index
- int nextTableIndex;// 下一個分段的容器的index
- HashEntry<K, V>[] currentTable;// 當前容器
- HashEntry<K, V> nextEntry;// 下個鍵值對
- HashEntry<K, V> lastReturned;// 上次返回的鍵值對
- HashIterator() {
- nextSegmentIndex = segments.length - 1;
- nextTableIndex = -1;
- advance();
- }
- publicboolean hasMoreElements() {
- return hasNext();
- }
- // 先變數鍵值對的連結串列,再對table 陣列的index 遍歷,最後遍歷分段陣列的index。。這樣就可以完整的變數完所有的entry了
- finalvoid advance() {
- // 先變數鍵值對的連結串列
- if (nextEntry != null && (nextEntry = nextEntry.next) != null)
- return;
- // 對table 陣列的index 遍歷
- while (nextTableIndex >= 0) {
- if ((nextEntry = currentTable[nextTableIndex--]) != null)
- return;
- }
- // 遍歷分段陣列的index
- while (nextSegmentIndex >= 0) {
- Segment<K, V> seg = segments[nextSegmentIndex--];
- if (seg.count != 0) {
- currentTable = seg.table;
- for (int j = currentTable.length - 1; j >= 0; --j) {
- if ((nextEntry = currentTable[j]) != null) {
- nextTableIndex = j - 1;
- return;
- }
- }
- }
- }
- }
- publicboolean hasNext() {
- return nextEntry != null;
- }
- HashEntry<K, V> nextEntry() {
- if (nextEntry == null)
- thrownew NoSuchElementException();
- // 把上次的entry換成當前的entry
- lastReturned = nextEntry;
- // 這裡做一些預操作
- advance();
- return lastReturned;
- }
- publicvoid remove() {
- if (lastReturned == null)
- thrownew IllegalStateException();
- ConcurrentHashMap.this.remove(lastReturned.key);
- lastReturned = null;
- }
- }
keySet/Values/elements 這幾個方法都和keys 方法非常相似。。就不解釋了。。而entrySet 方法有點特別。。我也有點不是很明白。。
Java程式碼- //這裡沒什麼好說的,看下就明白,主要在下面
- public Set<Map.Entry<K, V>> entrySet() {
- Set<Map.Entry<K, V>> es = entrySet;
- return (es != null) ? es : (entrySet = new EntrySet());
- }
- finalclass EntrySet extends AbstractSet<Map.Entry<K, V>> {
- public Iterator<Map.Entry<K, V>> iterator() {
- returnnew EntryIterator();
- }
- }
-
相關推薦
併發集合在分析之CurrentHashMap之從應用去分析,分段加鎖應用
實際應用: Java程式碼 ConcurrentMap<String, String> map = new ConcurrentHashMap<String, String>(); String oldValue = map.
python之使用set對列表去重,並保持列表原來順序
原來 div 無重復 列表 mys ddr body afa key #原始方法,但是會打亂順序 mylist = [1,2,2,2,2,3,3,3,4,4,4,4]myset = set(mylist) #myset是另外一個列表,裏面的內容是mylist裏面的無重復 項
雲計算從零開始學,雲計算的應用領域
box 中心 開始 三層 發展 有意思 ice dropbox 網絡架構 雲計算這個名詞來自於Google,而最早的雲計算產品來自於Amazon。有意思的是,Google在2006年正式提出雲計算這個名詞的時候,Amazon的雲計算產品AWS(Amazon Web Serv
語音識別之——mfcc什麼是漢明窗,為什麼加漢明窗
為什麼要加漢明窗?什麼叫加窗? 在訊號處理中,可以說加窗處理是一個必經的過程,因為我們的計算機只能處理有限長度的訊號,因此原始訊號X(t)要以T(取樣時間)截斷,即有限化,成為XT(t)後再進一步處理,這個過程式就是加窗處理,但什麼時候用什麼窗呢?這時我們就要對所需用到
從通知裡啟動應用的activity,返回時返回應用的主介面
其實這個開發主要是一種開發思路:我的思路大致是這樣的。 1. 首先在啟動這個具體的activity之前,判斷當前應用是否在棧頂,如果在,就不啟動主Activity了,如果不在則啟動主Activity;
一個人開發一個產品,小程式從0到1,第3章 應用檔案
一個小程式專案,在根目錄下會有3個應用檔案,一個是全域性業務邏輯檔案app.js,一個是公共配置檔案app.json,還有一個是公共樣式表文件app.wxss。在這3個檔案中,app.js和app.json是不可刪除,是必須有的檔案。 3.1 app.js 開啟檔案的那一瞬間,我才知道:人生遇到的人很多,但真
多執行緒,高併發的情況下操作redis當中的資料,如何加鎖?
多個執行緒同時去操作Redis當中的資料,假如不加鎖的情況下,會出現資料重複的問題。假如需要每次都只有一條執行緒去操作Redis當中的資料,需要給操作加上鎖。 但是去網上一搜,網上給Redis加鎖的機制都是利用Redis的setnx自身的方法去加鎖,但是這樣
iOS惡意應用欺騙使用者,將虛假的應用內購作為合法功能推送
發現蘋果iOS應用程式商店的兩款應用程式顯示惡意行為,目的是欺騙使用者在不知不覺中為隱藏在看似合法的應用程式功能背後的應用程式內購買支付。 據Reddit上的一篇文章稱,這兩款應用分別名為 "Calories Tracker app" 和 "Fitness Balance app,"它們
高併發之——從原始碼角度分析建立執行緒池究竟有哪些方式
前言 在Java的高併發領域,執行緒池一直是一個繞不開的話題。有些童鞋一直在使用執行緒池,但是,對於如何建立執行緒池僅僅停留在使用Executors工具類的方式,那麼,建立執行緒池究竟存在哪幾種方式呢?就讓我們一起從建立執行緒池的原始碼來深入分析究竟有哪些方式可以建立執行緒池。 使用Executors工具類建
Java內存模型之從JMM角度分析DCL
span 利用 eight first 多人 能夠 的人 ref upload DCL,即Double Check Lock,中衛雙重檢查鎖定。其實DCL很多人在單例模式中用過,LZ面試人的時候也要他們寫過,但是有很多人都會寫錯。他們為什麽會寫錯呢?其錯誤根源在哪裏?有什麽
Glide原始碼分析(二)——從用法來看之load&into方法
上一篇,我們分析了with方法,文章連結: https://blog.csdn.net/qq_36391075/article/details/82833260 在with方法中,進行了Glide的初始化,建立了RequesManger,並且綁定了生命週期,最終返回了一個Reques
Glide原始碼分析(一)從用法來看之with方法
繼續啃原始碼,用過Glide的人,肯定都覺得它好好用,我們一般只需要幾行程式碼,就可以達到我們想要的效果,可以在這個背後是什麼呢?就需要我們來看了。 我一般看原始碼,我喜歡先從用法來看,然後一步一步的再細扣,所以就先從用法來看Glide的整體流程。 用過Glide的人,用下面這段
java併發之原子操作類(AtomicLong原始碼分析)和非阻塞演算法
背景 近年來,在併發演算法領域的大多數研究都側重於非阻塞演算法,這種演算法用底層的原子機器指令(例如比較併發交換指令)代替鎖來確保資料在併發訪問中的一致性。非阻塞演算法被廣泛的用於在作業系統和JVM中實現執行緒/程序排程機制、垃圾回收機制以及鎖和其他併發資料結構。 與基於鎖
Bigemap--ArcGIS教程之DEM應用——水文分析
DEM之坡度坡向分析 Arcgis下DEM水文分析(二) 第一步:需要的工具 1. BIGEMPA地圖下載器 2. Global Mapper 14
Bigemap教程之ArcGISDEM應用——水文分析
相關教程: 第一步:需要的工具 1. BIGEMPA地圖下載器(全能版已授權) 2. Global Mapper 14.&
大資料之storm(一) --- storm簡介,核心元件,工作流程,安裝和部署,電話通訊案例分析,叢集執行,單詞統計案例分析,調整併發度
一、storm簡介 --------------------------------------------------------- 1.開源,分散式,實時計算 2.實時可靠的處理無限資料流,可以使用任何語言開發 3.適用於實時分析,線上機器學習
Android 效能分析之TraceView使用(應用耗時分析)
文章概覽: TraceView概述 trace檔案的3種生成方式 Android studio 直接生成(推薦) 嵌入程式碼程式碼生成 使用DDMS來生成 TraceView介面及引數介紹 使用TraceView分析,定位問題 相關資料 Trace
java多執行緒之併發集合(CopyOnWriteArrayList)
CopyOnWriteArrayList:CopyOnWriteArrayList這是一個ArrayList的執行緒安全的變體,其原理大概可以通俗的理解為:初始化的時候只有一個容器,很常一段時間,這個容器資料、數量等沒有發生變化的時候,大家(多個執行緒),都是讀取(假設這段時
Oracle官方併發教程之併發集合
原文地址 譯文地址 譯者:李任 校對:方騰飛 java.util.concurrent包囊括了Java集合框架的一些附加類。它們也最容易按照集合類所提供的介面來進行分類: BlockingQueue定義了一個先進先出的資料結構,當你嘗試往滿佇列中新增元素,或者從空佇列中獲取元素時,將會阻
Java設計模式之從[Dota地圖]分析享元(Flyweight)模式
在Dota遊戲的地圖中有幾百棵樹,現在假設這些樹木無非是這兩種:白楊、楓樹,數量一共為400棵,那麼,在裝載這個地圖場景的時候,我們是不是應該給這400課樹一一建立物件呢?(如:MapItem tree1 = new MapItem("白楊");MapItem tree