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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

谈谈HashMap线程不安全的体现

發布時間:2023/12/3 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 谈谈HashMap线程不安全的体现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載自?談談HashMap線程不安全的體現

HashMap的原理以及如何實現,之前在JDK7與JDK8中HashMap的實現中已經說明了。

那么,為什么說HashMap是線程不安全的呢?它在多線程環境下,會發生什么情況呢?

1. resize死循環

我們都知道HashMap初始容量大小為16,一般來說,當有數據要插入時,都會檢查容量有沒有超過設定的thredhold,如果超過,需要增大Hash表的尺寸,但是這樣一來,整個Hash表里的元素都需要被重算一遍。這叫rehash,這個成本相當的大。

12345678910111213void resize(int newCapacity) {????????Entry[] oldTable = table;????????int oldCapacity = oldTable.length;????????if (oldCapacity == MAXIMUM_CAPACITY) {????????????threshold = Integer.MAX_VALUE;????????????return;????????}????????Entry[] newTable = new Entry[newCapacity];????????transfer(newTable, initHashSeedAsNeeded(newCapacity));????????table = newTable;????????threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);}
123456789101112131415void 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:

  • 對索引數組中的元素遍歷
  • 對鏈表上的每一個節點遍歷:用 next 取得要轉移那個元素的下一個,將 e 轉移到新 Hash 表的頭部,使用頭插法插入節點。
  • 循環2,直到鏈表節點全部轉移
  • 循環1,直到所有索引數組全部轉移
  • 經過這幾步,我們會發現轉移的時候是逆序的。假如轉移前鏈表順序是1->2->3,那么轉移后就會變成3->2->1。這時候就有點頭緒了,死鎖問題不就是因為1->2的同時2->1造成的嗎?所以,HashMap 的死鎖問題就出在這個transfer()函數上。

    1.1?單線程 rehash 詳細演示

    單線程情況下,rehash 不會出現任何問題:

    • 假設hash算法就是最簡單的 key mod table.length(也就是數組的長度)。
    • 最上面的是old hash 表,其中的Hash表的 size = 2, 所以 key = 3, 7, 5,在 mod 2以后碰撞發生在 table[1]
    • 接下來的三個步驟是 Hash表 resize 到4,并將所有的?<key,value>?重新rehash到新 Hash 表的過程

    如圖所示:

    ?

    1.2?多線程 rehash 詳細演示

    為了思路更清晰,我們只將關鍵代碼展示出來

    123456while(null != e) {????Entry<K,V> next = e.next;????e.next = newTable[i];????newTable[i] = e;????e = next;}
  • Entry<K,V> next = e.next;——因為是單鏈表,如果要轉移頭指針,一定要保存下一個結點,不然轉移后鏈表就丟了
  • e.next = newTable[i];——e 要插入到鏈表的頭部,所以要先用 e.next 指向新的 Hash 表第一個元素(為什么不加到新鏈表最后?因為復雜度是 O(N))
  • newTable[i] = e;——現在新 Hash 表的頭指針仍然指向 e 沒轉移前的第一個元素,所以需要將新 Hash 表的頭指針指向 e
  • e = next——轉移 e 的下一個結點
  • 假設這里有兩個線程同時執行了put()操作,并進入了transfer()環節

    123456while(null != e) {????Entry<K,V> next = e.next; //線程1執行到這里被調度掛起了????e.next = newTable[i];????newTable[i] = e;????e = next;}

    那么現在的狀態為:

    ?

    從上面的圖我們可以看到,因為線程1的 e 指向了 key(3),而 next 指向了 key(7),在線程2 rehash 后,就指向了線程2 rehash 后的鏈表。

    然后線程1被喚醒了:

  • 執行e.next = newTable[i],于是 key(3)的 next 指向了線程1的新 Hash 表,因為新 Hash 表為空,所以e.next = null,
  • 執行newTable[i] = e,所以線程1的新 Hash 表第一個元素指向了線程2新 Hash 表的 key(3)。好了,e 處理完畢。
  • 執行e = next,將 e 指向 next,所以新的 e 是 key(7)
  • 然后該執行 key(3)的 next 節點 key(7)了:

  • 現在的 e 節點是 key(7),首先執行Entry<K,V> next = e.next,那么 next 就是 key(3)了
  • 執行e.next = newTable[i],于是key(7) 的 next 就成了 key(3)
  • 執行newTable[i] = e,那么線程1的新 Hash 表第一個元素變成了 key(7)
  • 執行e = next,將 e 指向 next,所以新的 e 是 key(3)
  • 這時候的狀態圖為:

    ?

    然后又該執行 key(7)的 next 節點 key(3)了:

  • 現在的 e 節點是 key(3),首先執行Entry<K,V> next = e.next,那么 next 就是 null
  • 執行e.next = newTable[i],于是key(3) 的 next 就成了 key(7)
  • 執行newTable[i] = e,那么線程1的新 Hash 表第一個元素變成了 key(3)
  • 執行e = next,將 e 指向 next,所以新的 e 是 key(7)
  • 這時候的狀態如圖所示:

    ?

    很明顯,環形鏈表出現了!!當然,現在還沒有事情,因為下一個節點是 null,所以transfer()就完成了,等put()的其余過程搞定后,HashMap 的底層實現就是線程1的新 Hash 表了。

    2. fail-fast

    如果在使用迭代器的過程中有其他線程修改了map,那么將拋出ConcurrentModificationException,這就是所謂fail-fast策略。

    這個異常意在提醒開發者及早意識到線程安全問題,具體原因請查看ConcurrentModificationException的原因以及解決措施

    順便再記錄一個HashMap的問題:

    為什么String, Interger這樣的wrapper類適合作為鍵??String, Interger這樣的wrapper類作為HashMap的鍵是再適合不過了,而且String最為常用。因為String是不可變的,也是final的,而且已經重寫了equals()和hashCode()方法了。其他的wrapper類也有這個特點。不可變性是必要的,因為為了要計算hashCode(),就要防止鍵值改變,如果鍵值在放入時和獲取時返回不同的hashcode的話,那么就不能從HashMap中找到你想要的對象。不可變性還有其他的優點如線程安全。如果你可以僅僅通過將某個field聲明成final就能保證hashCode是不變的,那么請這么做吧。因為獲取對象的時候要用到equals()和hashCode()方法,那么鍵對象正確的重寫這兩個方法是非常重要的。如果兩個不相等的對象返回不同的hashcode的話,那么碰撞的幾率就會小些,這樣就能提高HashMap的性能。

    Reference:

    1.?http://hwl-sz.iteye.com/blog/1897468?utm_source=tuicool&utm_medium=referral

    2.?http://github.thinkingbar.com/hashmap-infinite-loop/

    3.?http://www.importnew.com/7099.html



    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的谈谈HashMap线程不安全的体现的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。