ConcurrentMap.putIfAbsent(key,value)用法實(shí)例
本文研究的主要是ConcurrentMap.putIfAbsent(key,value)
用法的相關(guān)內(nèi)容,具體如下。
業(yè)務(wù)上經(jīng)常會(huì)遇到有這種場景,全局維護(hù)一個(gè)并發(fā)的ConcurrentMap, Map的每個(gè)Key對(duì)應(yīng)一個(gè)對(duì)象,這個(gè)對(duì)象需要只創(chuàng)建一次。如果Map中該key對(duì)應(yīng)的value不存在則創(chuàng)建,否則直接返回。
我們先看一下代碼:
public static Locale getInstance(String language, String country, String variant) { //... String key = some_string; Locale locale = map.get(key); if (locale == null) { locale = new Locale(language, country, variant); map.put(key, locale); } return locale; }
這段代碼要做的事情是:
- 調(diào)用 map.get(key) 方法,判斷 map 里面是否有該 key 對(duì)應(yīng)的 value (Locale 對(duì)象)。
- 如果返回 null,表示 map 里面沒有要查找的 key-value mapping。new 一個(gè) Locale 對(duì)象,并把 new 出來的這個(gè)對(duì)象與 key 一起放入 map。
- 最后返回新創(chuàng)建的 Locale 對(duì)象
我們期望每次調(diào)用 getInstance 方法時(shí)要保證相同的 key 返回同一個(gè) Local 對(duì)象引用。這段代碼能實(shí)現(xiàn)這個(gè)需求嗎?
答案是:在單線程環(huán)境下可以滿足要求,但是在多線程環(huán)境下會(huì)存在線程安全性問題,即不能保證在并發(fā)的情況相同的 key 返回同一個(gè) Local 對(duì)象引用。
這是因?yàn)樵谏厦娴拇a里存在一個(gè)習(xí)慣被稱為 put-if-absent 的操作 [1],而這個(gè)操作存在一個(gè) race condition:
if (locale == null) { locale = new Locale(language, country, variant); map.put(key, locale); }
因?yàn)樵谀硞€(gè)線程做完 locale == null 的判斷到真正向 map 里面 put 值這段時(shí)間,其他線程可能已經(jīng)往 map 做了 put 操作,這樣再做 put 操作時(shí),同一個(gè) key 對(duì)應(yīng)的 locale 對(duì)象被覆蓋掉,最終 getInstance 方法返回的同一個(gè) key 的 locale 引用就會(huì)出現(xiàn)不一致的情形。所以對(duì) Map 的 put-if-absent 操作是不安全的(thread safty)。
為了解決這個(gè)問題,java 5.0 引入了 ConcurrentMap 接口,在這個(gè)接口里面 put-if-absent 操作以原子性方法 putIfAbsent(K key, V value)
的形式存在。正如 javadoc 寫的那樣:
putIfAbsent方法主要是在向ConcurrentHashMap中添加鍵—值對(duì)的時(shí)候,它會(huì)先判斷該鍵值對(duì)是否已經(jīng)存在。
- 如果不存在(新的entry),那么會(huì)向map中添加該鍵值對(duì),并返回null。
- 如果已經(jīng)存在,那么不會(huì)覆蓋已有的值,直接返回已經(jīng)存在的值。
對(duì)上面方法進(jìn)行改造:
public static Locale getInstance(String language, String country, String variant) { //... String key = some_string; Locale locale = map.get(key); if (locale == null) { locale = new Locale(language, country, variant); map.putIfAbsent(key, locale); } return locale; }
這段代碼使用了 Map 的 concurrent 形式(ConcurrentMap、ConcurrentHashMap),并簡單的使用了語句map.putIfAbsent(key, locale)
。這同樣不能保證相同的 key 返回同一個(gè) Locale 對(duì)象引用。
這里的錯(cuò)誤出在忽視了 putIfAbsent 方法是有返回值的,并且返回值很重要。
所以,使用 putIfAbsent 方法時(shí)切記要對(duì)返回值進(jìn)行判斷。
public static Locale getInstance(String language, String country, String variant) { //... String key = some_string; Locale locale = map.get(key); if (locale == null) { locale = new Locale(language, country, variant); Locale tmp = map.putIfAbsent(key, locale); if (tmp != null) { locale = tmp; } } return locale; }
【實(shí)例1】
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class Test { public static void main(String[] args) { //測試一下currentHashMap.putIfAbsent() Map<long, String> clientMap = new ConcurrentHashMap<>(); System.out.println("首先打印空的clientMap"); System.out.println("clientMap: " + clientMap); System.out.println(); //在空的clientMap中添加一個(gè)新的記錄 System.out.println("在空的clientMap中添加一個(gè)新的記錄"); System.out.println("添加之前的clientMap: " + clientMap); long netId = 1234567L; String str1 = "michael"; String result = clientMap.putIfAbsent(netId, str1); System.out.println("添加之后的clientMap: " + clientMap); System.out.println("查看返回值result: " + result); System.out.println(); //重復(fù)添加 System.out.println("重復(fù)添加上一次的記錄"); System.out.println("添加之前的clientMap: " + clientMap); String result2 = clientMap.putIfAbsent(netId, str1); System.out.println("添加之后的clientMap: " + clientMap); System.out.println("查看返回值result: " + result2); System.out.println(); } }
總結(jié)
以上就是本文關(guān)于ConcurrentMap.putIfAbsent(key,value)用法實(shí)例的全部內(nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
Java?詳解Collection集合之ArrayList和HashSet
本章具體介紹了ArrayList和HashSet兩種集合的基本使用方法和區(qū)別,圖解穿插代碼實(shí)現(xiàn)。?JAVA成仙路從基礎(chǔ)開始講,后續(xù)會(huì)講到JAVA高級(jí),中間會(huì)穿插面試題和項(xiàng)目實(shí)戰(zhàn),希望能給大家?guī)韼椭?/div> 2022-03-03Java 反射調(diào)用靜態(tài)方法的簡單實(shí)例
下面小編就為大家?guī)硪黄狫ava 反射調(diào)用靜態(tài)方法的簡單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06解決javaBean規(guī)范導(dǎo)致json傳參首字母大寫將永遠(yuǎn)獲取不到問題
這篇文章主要介紹了解決javaBean規(guī)范導(dǎo)致json傳參首字母大寫將永遠(yuǎn)獲取不到問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07idea顯示springboot多服務(wù)啟動(dòng)界面service操作
這篇文章主要介紹了idea顯示springboot多服務(wù)啟動(dòng)界面service操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09spring的TransactionSynchronizationAdapter事務(wù)源碼解析
這篇文章主要介紹了spring的TransactionSynchronizationAdapter事務(wù)源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09關(guān)于@ComponentScan?TypeFilter自定義指定掃描bean的規(guī)則
這篇文章主要介紹了關(guān)于@ComponentScan?TypeFilter自定義指定掃描bean的規(guī)則,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09最新評(píng)論