我是🌟廖志偉🌟,一名🌕Java開發工程師🌕、📝Java領域優質創作者📝、🎉CSDN博客專家🎉、🌹幕后大佬社區創始人🌹。擁有多年一線研發經驗,研究過各種常見框架及中間件的底層源碼,對於大型分布式、微服務、三高架構(高性能、高並發、高可用)有過實踐架構經驗。
🍊博主:java_wxid
🍊博主:Java廖志偉
🍊社區:幕后大佬
本文的大概內容:
HashMap並發修改異常
HashMap實際使用過程中會出現一些線程安全問題,在JDK1.7中,當並發執行擴容操作時會造成環形鏈和數據丟失的情況,開多個線程不斷進行put操作,rehash的時候,舊鏈表遷移新鏈表的時候,如果在新表的數組索引位置相同,則鏈表元素會倒置(就是因為頭插)所以最后的結果打亂了插入的順序,就可能發生環形鏈和數據丟失的問題,引起死循環,導致CPU利用率接近100%。在jdk1.8中對HashMap進行了優化,發生hash碰撞,不再采用頭插法方式,而是直接插入鏈表尾部,因此不會出現環形鏈表的情況,但是在多線程環境下,會發生數據覆蓋的情況,如果沒有hash碰撞的時候,它會直接插入元素。如果線程A和線程B同時進行put操作,剛好這兩條不同的數據hash值一樣,並且該位置數據為null,線程A進入后還未進行數據插入時掛起,而線程B正常執行,從而正常插入數據,然后線程A獲取CPU時間片,此時線程A不用再進行hash判斷了,線程A會把線程B插入的數據給覆蓋,導致數據發生覆蓋的情況,發生線程不安全。可參考【HashMap擴容機制】
實際的故障現象:java.util.ConcurrentModificationException並發修改異常。導致原因:並發爭取修改導致,一個線程正在寫,一個線程過來爭搶,導致線程寫的過程被其他線程打斷,導致數據不一致。
使用HashTable
HashTable是線程安全的,只不過實現代價卻太大了,簡單粗暴,get/put所有相關操作都是synchronized的,這相當於給整個哈希表加了一把大鎖。多線程訪問時候,只要有一個線程訪問或操作該對象,那其他線程只能阻塞,相當於將所有的操作串行化,在競爭激烈的並發場景中性能就會非常差。
使用工具類
線程同步:Map<String,String> hashMap = Collections.synchronizedMap(new HashMap<>());
和Hashtable一樣,實現上在操作HashMap時自動添加了synchronized來實現線程同步,都對整個map進行同步,在性能以及安全性方面不如ConcurrentHashMap。
使用寫時復制(CopyOnWrite)
往一個容器里面加元素的時候,不直接往當前容器添加,而是先將當前容器的元素復制出來放到一個新的容器中,然后新的元素添加元素,添加完之后,再將原來容器的引用指向新的容器,這樣就可以對它進行並發的讀,不需要加鎖,因為當前容器不添加任何元素。利用了讀寫分離的思想,讀和寫是不同的容器。
會有內存占用問題,在復制的時候只是復制容器里的引用,只是在寫的時候會創建新對象添加到新容器里,而舊容器的對象還在使用,所以有兩份對象內存。
會有數據一致性問題,CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。
使用ConcurrentHashMap
為了應對hashmap在並發環境下不安全問題可以使用,ConcurrentHashMap大量的利用了volatile,CAS等技術來減少鎖競爭對於性能的影響。在JDK1.7版本中ConcurrentHashMap避免了對全局加鎖,改成了局部加鎖(分段鎖),分段鎖技術,將數據分成一段一段的存儲,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問,能夠實現真正的並發訪問。不過這種結構的帶來的副作用是Hash的過程要比普通的HashMap要長。
所以在JDK1.8版本中CurrentHashMap內部中的value使用volatile修飾,保證並發的可見性以及禁止指令重排,只不過volatile不保證原子性,使用為了確保原子性,采用CAS(比較交換)這種樂觀鎖來解決。
fast-fail機制
在系統設計中,快速失效系統一種可以立即報告任何可能表明故障的情況的系統。快速失效系統通常設計用於停止正常操作,而不是試圖繼續可能存在缺陷的過程。這種設計通常會在操作中的多個點檢查系統的狀態,因此可以及早檢測到任何故障。快速失敗模塊的職責是檢測錯誤,然后讓系統的下一個最高級別處理錯誤。其實就是在做系統設計的時候先考慮異常情況,一旦發生異常,直接停止並上報。
以下的代碼是一個對兩個整數做除法的方法,在fast_fail_method方法中,我們對被除數做了個簡單的檢查,如果其值為0,那么就直接拋出一個異常,並明確提示異常原因。這其實就是fail-fast理念的實際應用。
public int fast_fail_method(int arg1,int arg2){
if(arg2 == 0){
throw new RuntimeException("can't be zero");
}
return arg1/arg2;
}
在Java集合類中很多地方都用到了該機制進行設計,一旦使用不當,觸發fail-fast機制設計的代碼,就會發生非預期情況。我們通常說的Java中的fail-fast機制,默認指的是Java集合的一種錯誤檢測機制。當多個線程對部分集合進行結構上的改變的操作時,有可能會觸發該機制時,之后就會拋出並發修改異常ConcurrentModificationException。當然如果不在多線程環境下,如果在foreach遍歷的時候使用add/remove方法,也可能會拋出該異常。
之所以會拋出ConcurrentModificationException異常,是因為我們的代碼中使用了增強for循環,而在增強for循環中,集合遍歷是通過iterator進行的,但是元素的add/remove卻是直接使用的集合類自己的方法。這就導致iterator在遍歷的時候,會發現有一個元素在自己不知不覺的情況下就被刪除/添加了,就會拋出一個異常,用來提示可能發生了並發修改!所以,在使用Java的集合類的時候,如果發生ConcurrentModificationException,優先考慮fail-fast有關的情況,實際上這可能並沒有真的發生並發,只是Iterator使用了fail-fast的保護機制,只要他發現有某一次修改是未經過自己進行的,那么就會拋出異常。
總結
以上就是今天要講的內容,還希望各位讀者大大能夠在評論區積極參與討論,給文章提出一些寶貴的意見或者建議📝,合理的內容,我會采納更新博文,重新分享給大家。
🙏四連 關注🔎點贊👍收藏⭐️留言📝
感謝大家的支持,用心寫博文分享給大家,你的支持(🔎點贊👍收藏⭐️留言📝)是對我創作的最大幫助。
🍊微信公眾號:南北踏塵
🍊主頁地址:java_wxid
🍊社區地址:幕后大佬
給讀者大大的話
我本身是一個很普通的程序員,放在人堆里,除了與生俱來的🌹盛世美顏🌹、所剩不多的發量,就剩下180的大高個了。就是我這樣的一個人,默默堅持寫博文也有好多年了,有句老話說的好,🌕牛逼之前都是傻逼式的堅持🌕。希望自己可以通過大量的作品,時間的積累,個人魅力、運氣和時機,可以打造屬於自己的🌟技術影響力🌟。同時也希望自己可以成為一個🎄懂技術🎄,🎄懂業務🎄,🎄懂管理🎄的綜合型人才,作為項目架構路線的總設計師,掌控全局的🌕團隊大腦🌕,技術團隊中的🍊絕對核心🍊是我未來幾年不斷前進的目標。
提示:以下都是資源分享,求個一鍵三連。
面試資料
福利大放送,🎉歡迎關注🔎點贊👍收藏⭐️留言📝,拜托了🙏,這對我真的很重要。
點擊:面試資料
提取碼:2021
200套PPT模板
福利大放送,🎉歡迎關注🔎點贊👍收藏⭐️留言📝,拜托了🙏,這對我真的很重要。
點擊:200套PPT模板
提取碼:2021
提問的智慧
福利大放送,🎉歡迎關注🔎點贊👍收藏⭐️留言📝,拜托了🙏,這對我真的很重要。
點擊:提問的智慧
提取碼:2021
Java開發學習路線
名稱 | 鏈接 |
---|---|
JavaSE | 點擊: JavaSE |
MySQL專欄 | 點擊: MySQL專欄 |
JDBC專欄 | 點擊: JDBC專欄 |
MyBatis專欄 | 點擊: MyBatis專欄 |
Web專欄 | 點擊: Web專欄 |
Spring專欄 | 點擊: Spring專欄 |
SpringMVC專欄 | 點擊: SpringMVC專欄 |
SpringBoot專欄 | 點擊: SpringBoot專欄 |
SpringCould專欄 | 點擊: SpringCould專欄 |
Redis專欄 | 點擊: Redis專欄 |
Linux專欄 | 點擊: Linux專欄 |
Maven3專欄 | 點擊: Maven3專欄 |
Spring Security5專欄 | 點擊: Spring Security5專欄 |
更多專欄 | 更多專欄,請到 java_wxid主頁 查看 |
P5學習路線圖
P6學習路線圖
P7學習路線圖
P8學習路線圖
以上四張圖詳細介紹了作為Java開發工作者所需要具備的知識技能,同學們學廢了嘛,有想法系統學習的同學可以私聊我,🎉歡迎關注🔎點贊👍收藏⭐️留言📝。
🍊博主:java_wxid
🍊博主:Java廖志偉
🍊社區:幕后大佬