并發(fā)容器 ConcurrentHashMap
1. 前言
從本節(jié)開始,我們學(xué)習(xí)新一章內(nèi)容 —— 并發(fā)容器。
本節(jié)帶領(lǐng)大家認(rèn)識第一個常用的 Java 并發(fā)容器類之 ConcurrentHashMap。
本節(jié)先介紹 ConcurrentHashMap 工具類表達的概念和最基本用法,接著通過一個例子為大家解釋 ConcurrentHashMap 工具類的使用場合并通過簡單的編碼實現(xiàn)此場景,最后介紹 ConcurrentHashMap 提供的幾個其他常用方法。
下面我們正式開始介紹吧。
2. 概念解釋
Concurrent 翻譯過來是并發(fā)的意思,字面理解它的作用就是提供并發(fā)情況下的 HashMap 功能,ConcurrentHashMap 是對 HashMap 的升級,采用了分段加鎖而非全局加鎖的策略,增強了 HashMap 非線程安全的特征,同時提高了并發(fā)度。我們通過一張圖片了解一下 ConcurrentHashMap 的邏輯結(jié)構(gòu)。
概念已經(jīng)了解了,ConcurrentHashMap 工具類最基本的用法是怎樣的呢?看下面。
3. 基本用法
// 創(chuàng)建一個 ConcurrentHashMap 對象
ConcurrentHashMap<Object, Object> concurrentHashMap = new ConcurrentHashMap<>();
// 添加鍵值對
concurrentHashMap.put("key", "value");
// 添加一批鍵值對
concurrentHashMap.putAll(new HashMap());
// 使用指定的鍵獲取值
concurrentHashMap.get("key");
// 判定是否為空
concurrentHashMap.isEmpty();
// 獲取已經(jīng)添加的鍵值對個數(shù)
concurrentHashMap.size();
// 獲取已經(jīng)添加的所有鍵的集合
concurrentHashMap.keys();
// 獲取已經(jīng)添加的所有值的集合
concurrentHashMap.values();
// 清空
concurrentHashMap.clear();
是不是很簡單,那 ConcurrentHashMap 應(yīng)用在哪些場合比較合適呢?下面我們給出最常用的場景說明。
4. 常用場景
我們在多線程場合下需要共同操作一個 HashMap 對象的時候,可以直接使用 ConcurrentHashMap 類型而不用再自行做任何并發(fā)控制,當(dāng)然也可以使用最常見的 synchronized 對 HashMap 進行封裝。推薦直接使用 ConcurrentHashMap ,是僅僅因為其安全,相比全局加鎖的方式而且很高效,還有很多已經(jīng)提供好的簡便方法,不用我們自己再另行實現(xiàn)。
舉一個日常研發(fā)中常見的例子:統(tǒng)計 4 個文本文件中英文字母出現(xiàn)的總次數(shù)。為了加快統(tǒng)計處理效率,采用 4 個線程每個線程處理 1 個文件的方式。此場合下統(tǒng)計結(jié)果是多個鍵值對,鍵是單詞,值是字母出現(xiàn)的總次數(shù),采用 Map 數(shù)據(jù)結(jié)構(gòu)存放統(tǒng)計結(jié)果最合適??紤]到多線程同時操作同一個 Map 進行統(tǒng)計結(jié)果更新,我們應(yīng)該采用 ConcurrentHashMap 最合適。請看下面代碼。
5. 場景案例
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
public class ConcurrentHashMapTest {
// 創(chuàng)建一個 ConcurrentHashMap 對象用于存放統(tǒng)計結(jié)果
private static ConcurrentHashMap<String, AtomicLong> concurrentHashMap = new ConcurrentHashMap<>();
// 創(chuàng)建一個 CountDownLatch 對象用于統(tǒng)計線程控制
private static CountDownLatch countDownLatch = new CountDownLatch(3);
// 模擬文本文件中的單詞
private static String[] words = {"we", "it", "is"};
public static void main(String[] args) throws InterruptedException {
Runnable task = new Runnable() {
public void run() {
for(int i=0; i<3; i++) {
// 模擬從文本文件中讀取到的單詞
String word = words[new Random().nextInt(3)];
// 嘗試獲取全局統(tǒng)計結(jié)果
AtomicLong number = concurrentHashMap.get(word);
// 在未獲取到的情況下,進行初次統(tǒng)計結(jié)果設(shè)置
if (number == null) {
// 在設(shè)置時如果發(fā)現(xiàn)如果不存在則初始化
AtomicLong newNumber = new AtomicLong(0);
number = concurrentHashMap.putIfAbsent(word, newNumber);
if (number == null) {
number = newNumber;
}
}
// 在獲取到的情況下,統(tǒng)計次數(shù)直接加1
number.incrementAndGet();
System.out.println(Thread.currentThread().getName() + ":" + word + " 出現(xiàn)" + number + " 次");
}
countDownLatch.countDown();
}
};
new Thread(task, "線程1").start();
new Thread(task, "線程2").start();
new Thread(task, "線程3").start();
try {
countDownLatch.await();
System.out.println(concurrentHashMap.toString());
} catch (Exception e) {}
}
}
觀察輸出的結(jié)果如下:
線程1:is 出現(xiàn)1 次
線程2:is 出現(xiàn)2 次
線程2:it 出現(xiàn)1 次
線程2:it 出現(xiàn)2 次
線程1:is 出現(xiàn)3 次
線程1:is 出現(xiàn)4 次
線程3:is 出現(xiàn)5 次
線程3:we 出現(xiàn)1 次
線程3:is 出現(xiàn)6 次
{is=6, it=2, we=1}
其實 ConcurrentHashMap 在使用方式方面和 HashMap 很類似,只是其底層封裝了線程安全的控制邏輯。
6. 幾個其他方法介紹
-
V putIfAbsent(K key, V value)
如果 key 對應(yīng)的 value 不存在,則 put 進去,返回 null。否則不 put,返回已存在的 value。 -
boolean remove(Object key, Object value)
如果 key 對應(yīng)的值是 value,則移除 K-V,返回 true。否則不移除,返回 false。 -
boolean replace(K key, V oldValue, V newValue)
如果 key 對應(yīng)的當(dāng)前值是 oldValue,則替換為 newValue,返回 true。否則不替換,返回 false。
7. 小結(jié)
本節(jié)通過一個簡單的例子,介紹了 ConcurrentHashMap 的使用場景和基本用法。希望大家在學(xué)習(xí)過程中,多思考勤練習(xí),早日掌握之。