本文研究的主要是ConcurrentMap.putIfAbsent(key,value)
用法的相關(guān)內(nèi)容,具體如下。
業(yè)務(wù)上經(jīng)常會遇到有這種場景,全局維護一個并發(fā)的ConcurrentMap, Map的每個Key對應(yīng)一個對象,這個對象需要只創(chuàng)建一次。如果Map中該key對應(yīng)的value不存在則創(chuàng)建,否則直接返回。
我們先看一下代碼:
1
2
3
4
5
6
7
8
9
10
11
|
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 對應(yīng)的 value (Locale 對象)。
- 如果返回 null,表示 map 里面沒有要查找的 key-value mapping。new 一個 Locale 對象,并把 new 出來的這個對象與 key 一起放入 map。
- 最后返回新創(chuàng)建的 Locale 對象
我們期望每次調(diào)用 getInstance 方法時要保證相同的 key 返回同一個 Local 對象引用。這段代碼能實現(xiàn)這個需求嗎?
答案是:在單線程環(huán)境下可以滿足要求,但是在多線程環(huán)境下會存在線程安全性問題,即不能保證在并發(fā)的情況相同的 key 返回同一個 Local 對象引用。
這是因為在上面的代碼里存在一個習(xí)慣被稱為 put-if-absent 的操作 [1],而這個操作存在一個 race condition:
1
2
3
4
|
if (locale == null ) { locale = new Locale(language, country, variant); map.put(key, locale); } |
因為在某個線程做完 locale == null 的判斷到真正向 map 里面 put 值這段時間,其他線程可能已經(jīng)往 map 做了 put 操作,這樣再做 put 操作時,同一個 key 對應(yīng)的 locale 對象被覆蓋掉,最終 getInstance 方法返回的同一個 key 的 locale 引用就會出現(xiàn)不一致的情形。所以對 Map 的 put-if-absent 操作是不安全的(thread safty)。
為了解決這個問題,java 5.0 引入了 ConcurrentMap 接口,在這個接口里面 put-if-absent 操作以原子性方法 putIfAbsent(K key, V value)
的形式存在。正如 javadoc 寫的那樣:
putIfAbsent方法主要是在向ConcurrentHashMap中添加鍵—值對的時候,它會先判斷該鍵值對是否已經(jīng)存在。
- 如果不存在(新的entry),那么會向map中添加該鍵值對,并返回null。
- 如果已經(jīng)存在,那么不會覆蓋已有的值,直接返回已經(jīng)存在的值。
對上面方法進行改造:
1
2
3
4
5
6
7
8
9
10
11
|
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 返回同一個 Locale 對象引用。
這里的錯誤出在忽視了 putIfAbsent 方法是有返回值的,并且返回值很重要。
所以,使用 putIfAbsent 方法時切記要對返回值進行判斷。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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; } |
【實例1】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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中添加一個新的記錄 System.out.println( "在空的clientMap中添加一個新的記錄" ); 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)用法實例的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!
原文鏈接:http://blog.csdn.net/liuxiao723846/article/details/79034556