成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

你了解ConcurrentHashMap嗎?ConcurrentHashMap九連問

開發 前端
當iterator被創建后集合再發生改變就不再是拋出ConcurrentModificationException,取而代之的是在改變時new新的數據從而不影響原有的數據 ,iterator完成后再將頭指針替換為新的數據 ,這樣iterator線程可以使用原來老的數據,而寫線程也可以并發的完成改變。?

多線程環境下,使用Hashmap進行put操作會造成數據覆蓋,應該使用支持多線程的 ConcurrentHashMap。

HashMap為什么線程不安全

put的不安全

由于多線程對HashMap進行put操作,調用了HashMap的putVal(),具體原因:

  1. 假設兩個線程A、B都在進行put操作,并且hash函數計算出的插入下標是相同的;

當線程A執行完第六行由于時間片耗盡導致被掛起,而線程B得到時間片后在該下標處插入了元素,完成了正常的插入;

接著線程A獲得時間片,由于之前已經進行了hash碰撞的判斷,所有此時不會再進行判斷,而是直接進行插入;

最終就導致了線程B插入的數據被線程A覆蓋了,從而線程不安全。

  1. 代碼的第38行處有個++size,線程A、B,這兩個線程同時進行put操作時,假設當前HashMap的zise大小為10;
  2. 當線程A執行到第38行代碼時,從主內存中獲得size的值為10后準備進行+1操作,但是由于時間片耗盡只好讓出CPU;
  3. 接著線程B拿到CPU后從主內存中拿到size的值10進行+1操作,完成了put操作并將size=11寫回主內存;
  4. 接著線程A再次拿到CPU并繼續執行(此時size的值仍為10),當執行完put操作后,還是將size=11寫回內存;
  5. 此時,線程A、B都執行了一次put操作,但是size的值只增加了1,所有說還是由于數據覆蓋又導致了線程不安全。
1 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
2            boolean evict) {
3  Node <K, V> [] tab; Node <K, V> p; int n, i;
4 if ((tab = table) == null || (n = tab.length) == 0)
5   n = (tab = resize()).length;
6 if ((p = tab[i = (n - 1) & hash]) == null) //
        tab[i] = newNode(hash, key, value, null);
    else {
        Node < K, V > e;
        K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode <K, V> ) p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0;; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    
38  if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

擴容不安全

Java7中頭插法擴容會導致死循環和數據丟失,Java8中將頭插法改為尾插法后死循環和數據丟失已經得到解決,但仍然有數據覆蓋的問題。

這是jdk7中存在的問題

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry <K, V> e: table) {
        while (null != e) {
            Entry <K, V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

transfer過程如下:

  1. 對索引數組中的元素遍歷
  2. 對鏈表上的每一個節點遍歷:用 next 取得要轉移那個元素的下一個,將 e 轉移到新 Hash 表的頭部,使用頭插法插入節點。
  3. 循環2,直到鏈表節點全部轉移
  4. 循環1,直到所有索引數組全部轉移

注意 e.next = newTable[i] 和newTable[i] = e 這兩行代碼,就會導致鏈表的順序翻轉。

擴容操作就是新生成一個新的容量的數組,然后對原數組的所有鍵值對重新進行計算和寫入新的數組,之后指向新生成的數組。當多個線程同時檢測到總數量超過門限值的時候就會同時調用resize操作,各自生成新的數組并rehash后賦給該map底層的數組table,結果最終只有最后一個線程生成的新數組被賦給table變量,其他線程的均會丟失。而且當某些線程已經完成賦值而其他線程剛開始的時候,就會用已經被賦值的table作為原始數組,這樣也會有問題。

Map m = Collections.synchronizedMap(new LinkedHashMap(...));

ConcurrentHashMap原理?put執行流程?

回顧hashMap的put方法過程

  1. 計算出key的槽位
  2. 根據槽位類型進行操作(鏈表,紅黑樹)
  3. 根據槽位中成員數量進行數據轉換,擴容等操作

圖片圖片

如何高效的執行并發操作:根據上面hashMap的數據結構可以直觀的看到,如果以整個容器為一個資源進行鎖定,那么就變為了串行操作。而根據hash表的特性,具有沖突的操作只會出現在同一槽位,而與其它槽位的操作互不影響。基于此種判斷,那么就可以將資源鎖粒度縮小到槽位上,這樣熱點一分散,沖突的概率就大大降低,并發性能就能得到很好的增強。

圖片圖片

總體上來說,就是采用 Node + CAS + synchronized 來保證并發安全。數據結構跟 HashMap 1.8 的結構類似,數組+鏈表/紅黑二叉樹。Java 8 在鏈表長度超過一定閾值(8)時將鏈表(尋址時間復雜度為 O(N))轉換為紅黑樹(尋址時間復雜度為 O(log(N)))。

Java 8 中,鎖粒度更細,synchronized 只鎖定當前鏈表或紅黑二叉樹的首節點,這樣只要 hash 不沖突,就不會產生并發,就不會影響其他 Node 的讀寫,效率大幅提升。

ConcurrentHashMap 的get 方法是否需要加鎖?

不需要加鎖。

通過 volatile 關鍵字,concurentHashmap能夠確保 get 方法的線程安全,即使在寫入發生時,讀取線程仍然能夠獲得最新的數據,不會引發并發問題

具體是通過 unsafe#getxxxvolatile 和用 volatile 來修飾節點的 val  和 next 指針來實現的。

ConcurrentHashMap 和 Hashtable 的區別?

相同點:ConcurrentHashMap 和 Hashtable 都是線程安全的,可以在多個線程同時訪問它們而不需要額外的同步措施。

不同點:

  1. Hashtable通過使用synchronized修飾方法的方式來實現多線程同步,因此,Hashtable的同步會鎖住整個數組。在高并發的情況下,性能會非常差。ConcurrentHashMap采用了使用數組+鏈表+紅黑樹數據結構和CAS原子操作實現;synchronized鎖住桶,以及大量的CAS操作來保證線程安全。
  2. Hashtable 讀寫操作都加鎖,ConcurrentHashMap的讀操作不加鎖,寫操作加鎖
  3. Hashtable默認的大小為11,當達到閾值后,每次按照下面的公式對容量進行擴充:newCapacity = oldCapacity * 2 + 1。ConcurrentHashMap默認大小是16,擴容時容量擴大為原來的2倍。
  4. Null 鍵和值:  ConcurrentHashMap 不允許存儲 null 鍵或 null 值,如果嘗試存儲 null 鍵或值,會拋出 NullPointerException。  Hashtable 也不允許存儲 null 鍵和值。

為什么JDK8不用ReentrantLock而用synchronized

  • 減少內存開銷:如果使用ReentrantLock則需要節點繼承AQS來獲得同步支持,增加內存開銷,而1.8中只有頭節點需要進行同步。
  • 內部優化:synchronized則是JVM直接支持的,JVM能夠在運行時作出相應的優化措施:鎖粗化、鎖消除、鎖自旋等等。

為什么key 和 value 不允許為 null

HashMap中,null可以作為鍵或者值都可以。而在ConcurrentHashMap中,key和value都不允許為null。

ConcurrentHashMap的作者——Doug Lea的解釋如下:

圖片圖片

主要意思就是說:

ConcurrentMap(如ConcurrentHashMap、ConcurrentSkipListMap)不允許使用null值的主要原因是,在非并發的Map中(如HashMap),是可以容忍模糊性(二義性)的,而在并發Map中是無法容忍的。

假如說,所有的Map都支持null的話,那么map.get(key)就可以返回null,但是,這時候就會存在一個不確定性,當你拿到null的時候,你是不知道他是因為本來就存了一個null進去還是說就是因為沒找到而返回了null。

在HashMap中,因為它的設計就是給單線程用的,所以當我們map.get(key)返回null的時候,我們是可以通過map.contains(key)檢查來進行檢測的,如果它返回true,則認為是存了一個null,否則就是因為沒找到而返回了null。

但是,像ConcurrentHashMap,它是為并發而生的,它是要用在并發場景中的,當我們map.get(key)返回null的時候,是沒辦法通過map.contains(key)(ConcurrentHashMap有這個方法,但不可靠)檢查來準確的檢測,因為在檢測過程中可能會被其他線程鎖修改,而導致檢測結果并不可靠。

所以,為了讓ConcurrentHashMap的語義更加準確,不存在二義性的問題,他就不支持null。

使用了ConcurrentHashMap 就能保證業務的線程安全嗎?

需要知道的是,集合線程安全并不等于業務線程安全,并不是說使用了線程安全的集合 如ConcurrentHashMap 就能保證業務的線程安全。這是因為,ConcurrentHashMap只能保證put時是安全的,但是在put操作前如果還有其他的操作,那業務并不一定是線程安全的。

例如存在復合操作,也就是存在多個基本操作(如put、get、remove、containsKey等)組成的操作,例如先判斷某個鍵是否存在containsKey(key),然后根據結果進行插入或更新put(key, value)。這種操作在執行過程中可能會被其他線程打斷,導致結果不符合預期。

例如,有兩個線程 A 和 B 同時對 ConcurrentHashMap 進行復合操作,如下:

// 線程 A
if (!map.containsKey(key)) {
 map.put(key, value);
}
// 線程 B
if (!map.containsKey(key)) {
 map.put(key, anotherValue);
}

如果線程 A 和 B 的執行順序是這樣:

  1. 線程 A 判斷 map 中不存在 key
  2. 線程 B 判斷 map 中不存在 key
  3. 線程 B 將 (key, anotherValue) 插入 map
  4. 線程 A 將 (key, value) 插入 map

那么最終的結果是 (key, value),而不是預期的 (key, anotherValue)。這就是復合操作的非原子性導致的問題。

那如何保證 ConcurrentHashMap 復合操作的原子性呢?

ConcurrentHashMap 提供了一些原子性的復合操作,如 putIfAbsent、compute、computeIfAbsent 、computeIfPresent、merge等。這些方法都可以接受一個函數作為參數,根據給定的 key 和 value 來計算一個新的 value,并且將其更新到 map 中。

上面的代碼可以改寫為:

// 線程 A
map.putIfAbsent(key, value);
// 線程 B
map.putIfAbsent(key, anotherValue);

或者:

// 線程 A
map.computeIfAbsent(key, k -> value);
// 線程 B
map.computeIfAbsent(key, k -> anotherValue);

很多同學可能會說了,這種情況也能加鎖同步呀!確實可以,但不建議使用加鎖的同步機制,違背了使用 ConcurrentHashMap 的初衷。在使用 ConcurrentHashMap 的時候,盡量使用這些原子性的復合操作方法來保證原子性。

SynchronizedMap和ConcurrentHashMap有什么區別?

SynchronizedMap一次鎖住整張表來保證線程安全,所以每次只能有一個線程來訪問map。

JDK1.8 ConcurrentHashMap采用CAS和synchronized來保證并發安全。數據結構采用數組+鏈表/紅黑二叉樹。synchronized只鎖定當前鏈表或紅黑二叉樹的首節點,支持并發訪問、修改。 另外ConcurrentHashMap使用了一種不同的迭代方式。當iterator被創建后集合再發生改變就不再是拋出ConcurrentModificationException,取而代之的是在改變時new新的數據從而不影響原有的數據 ,iterator完成后再將頭指針替換為新的數據 ,這樣iterator線程可以使用原來老的數據,而寫線程也可以并發的完成改變。

責任編輯:武曉燕 來源: SevenCoding
相關推薦

2023-09-28 21:37:41

HashMap多線程

2022-08-09 07:37:40

對象并發容器

2025-03-27 01:10:00

HashMap分段鎖CAS

2024-06-17 00:02:00

線程安全HashMapJDK 1.7

2015-06-15 10:12:36

Java原理分析

2022-02-16 14:20:46

HashTableHashMap線程安全

2021-06-01 10:49:22

線程池Java開發

2021-06-03 14:23:57

線程線程池JAVA

2021-04-27 08:54:43

ConcurrentH數據結構JDK8

2023-08-22 20:43:09

HashMap單線程null

2021-12-01 11:50:50

HashMap面試Java

2024-01-30 07:36:51

Javalocks

2024-10-31 11:16:19

高并發并發集JDK

2024-08-29 16:30:27

2020-09-30 18:19:27

RedisJava面試

2022-01-24 07:01:20

安全多線程版本

2022-05-08 18:18:40

JDKValueHashMap

2020-09-22 12:00:23

Javahashmap高并發

2020-10-26 07:02:11

ConcurrentH存儲

2022-07-26 00:00:22

HTAP系統數據庫
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人在线视频免费观看 | 在线欧美日韩 | 一级高清 | 免费三级黄 | 97国产爽爽爽久久久 | 欧美日韩精品久久久免费观看 | 久久精品91久久久久久再现 | 亚洲精品久久久一区二区三区 | 夜夜夜夜夜夜曰天天天 | 午夜视频在线免费观看 | 亚洲综合成人网 | h视频免费观看 | 久久国产激情视频 | 黄色骚片 | 久久91av| 国产福利在线 | 国产欧美三区 | av影音在线 | 中文字幕一区二区在线观看 | 欧美国产视频一区二区 | 91av入口| 在线看91| 久久九九网站 | 久久99精品久久久久婷婷 | 中文字幕一级 | 精品免费在线 | 激情三区 | 日韩欧美国产一区二区三区 | 中文字幕第十一页 | 国产一区二区三区 | 亚洲欧美中文日韩在线v日本 | 成年人在线观看 | 精品一区二区三区在线观看 | 久久久精品网站 | 国产欧美一区二区三区久久 | 射久久| 天天操 夜夜操 | 欧美中文字幕一区二区三区亚洲 | 亚洲va欧美va人人爽午夜 | 在线亚洲精品 | 久久久久中文字幕 |