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