ConcurrentHashMap原理及使用詳解
ConcurrentHashMap是Java中的一種線程安全的哈希表實(shí)現(xiàn),它提供了與Hashtable和HashMap類似的API,但通過使用分段鎖技術(shù)(Segment),使得多個(gè)線程可以同時(shí)讀取和寫入不同的數(shù)據(jù)塊,從而提高了并發(fā)性能。同時(shí),ConcurrentHashMap也支持弱一致性,即在某些情況下,讀取操作可能會(huì)返回稍早的值,但這對(duì)于很多應(yīng)用場(chǎng)景來說是可以接受的。該類還提供了一些有用的方法,如putIfAbsent()、replace()、compute()等,方便開發(fā)者進(jìn)行基于哈希表的數(shù)據(jù)處理??傊?,ConcurrentHashMap是一個(gè)高效且可靠的多線程環(huán)境下的哈希表實(shí)現(xiàn),非常適合在并發(fā)場(chǎng)景中使用。
一、ConcurrentHashMap 的數(shù)據(jù)結(jié)構(gòu)
ConcurrentHashMap 是 Java 中的一種線程安全的哈希表實(shí)現(xiàn),其數(shù)據(jù)結(jié)構(gòu)是由多個(gè) Node 節(jié)點(diǎn)組成的數(shù)組和鏈表或紅黑樹。
在 ConcurrentHashMap 中,Node 節(jié)點(diǎn)是一個(gè)鍵值對(duì),其中鍵為 K 類型,值為 V 類型。每個(gè)節(jié)點(diǎn)包含了一個(gè)哈希值、一個(gè)鍵和一個(gè)值,以及指向下一個(gè)節(jié)點(diǎn)的引用。具體而言,ConcurrentHashMap 的內(nèi)部數(shù)據(jù)結(jié)構(gòu)如下:

ConcurrentHashMap 中的每個(gè) Segment 是一個(gè)獨(dú)立的哈希表,而每個(gè) Segment 又由多個(gè) Bucket 組成,每個(gè) Bucket 再維護(hù)一個(gè)鏈表或紅黑樹(紅黑樹出現(xiàn)的條件是 Bucket 中的元素?cái)?shù)量大于等于 8)。
ConcurrentHashMap 的 put 操作可以分成兩個(gè)步驟,首先根據(jù) Key 的哈希值找到對(duì)應(yīng)的 Segment 和 Bucket,然后在 Bucket 中插入新的 Node 節(jié)點(diǎn)。具體來說,它的處理流程如下:
- 對(duì) Key 進(jìn)行哈希操作,得到其哈希值。
- 根據(jù)哈希值和 Segment 數(shù)組的長(zhǎng)度,計(jì)算出 Key 應(yīng)該被放在哪個(gè) Segment 中。
- 在 Segment 中獲取 Key 應(yīng)該放在哪個(gè) Bucket 中。
- 如果該 Bucket 是空的,則直接在它的頭部插入新的 Node 節(jié)點(diǎn);否則,將新的 Node 節(jié)點(diǎn)插入到鏈表的尾部或紅黑樹上,并根據(jù)情況進(jìn)行擴(kuò)容或紅黑樹轉(zhuǎn)換為鏈表。
- 如果插入新的節(jié)點(diǎn)后 Bucket 中的元素?cái)?shù)量超過了一個(gè)閾值,則需要進(jìn)行擴(kuò)容操作。
- 如果插入新的節(jié)點(diǎn)后 Bucket 中的元素?cái)?shù)量大于等于 8 個(gè)并且 Bucket 不是紅黑樹,則需要將鏈表轉(zhuǎn)換為紅黑樹。
- 如果舊的紅黑樹中的節(jié)點(diǎn)數(shù)量少于 6 個(gè),則需要將紅黑樹轉(zhuǎn)換為鏈表。
ConcurrentHashMap 的 get 操作也很簡(jiǎn)單,直接根據(jù) Key 的哈希值找到對(duì)應(yīng)的 Segment 和 Bucket,然后在 Bucket 中查找對(duì)應(yīng)的 Node 節(jié)點(diǎn)即可。
二、ConcurrentHashMap 的分段鎖機(jī)制
ConcurrentHashMap 將整個(gè)哈希表分為多個(gè) Segment,每個(gè) Segment 又是一個(gè)獨(dú)立的哈希表,可以單獨(dú)進(jìn)行加鎖和擴(kuò)容操作。在操作數(shù)據(jù)時(shí),只需要獲取對(duì)應(yīng) Segment 的鎖,不需要鎖住整個(gè)哈希表,這樣可以避免多個(gè)線程之間的等待和競(jìng)爭(zhēng),同時(shí)提高吞吐量和并發(fā)性能。
簡(jiǎn)單實(shí)現(xiàn)示例:
class ConcurrentHashMap<K, V> {
final Segment[] segments;
class Segment extends ReentrantLock implements Serializable{
// 每個(gè) Segment 自己獨(dú)立的哈希表
private final Map<K,V> map = new HashMap<>();
// Segment 內(nèi)部加鎖機(jī)制,確保線程安全
public synchronized V put(K key, V value) {
return map.put(key, value);
}
}
// 獲取 key 所屬的 Segment 的索引
private int getSegmentIndex(K key) {
int hash = hash(key.hashCode()); // 對(duì) hashcode() 進(jìn)行哈希
int segmentMask = segments.length - 1; // mask 值
return hash & segmentMask; // 按位與,定位具體的 Segment
}
public V put(K key, V value) {
Segment s = segments[getSegmentIndex(key)];
s.lock(); // 獲取 s 對(duì)應(yīng)的 Segment 的鎖
try {
return s.put(key, value); // 在 s 上進(jìn)行 put 操作
} finally {
s.unlock(); // 釋放 s 對(duì)應(yīng)的 Segment 的鎖
}
}
}在實(shí)際的 ConcurrentHashMap 中,每個(gè) Segment 會(huì)使用一個(gè)獨(dú)立的哈希表來維護(hù)其內(nèi)部的數(shù)據(jù),同時(shí)也具備自己的鎖機(jī)制,從而實(shí)現(xiàn)對(duì)其內(nèi)部狀態(tài)的并發(fā)安全訪問。這樣,不同線程訪問不同的 Segment 時(shí)可以通過分段鎖機(jī)制來實(shí)現(xiàn)并發(fā)訪問,從而提高了 ConcurrentHashMap 的并發(fā)性能和吞吐量。
三、ConcurrentHashMap 的實(shí)現(xiàn)過程
在 JDK 1.8 以前,ConcurrentHashMap 的實(shí)現(xiàn)采用了與 Hashtable 類似的分段鎖機(jī)制,每個(gè) Segment 都對(duì)應(yīng)一個(gè) ReentrantLock 鎖,用于并發(fā)訪問。
而在 JDK 1.8 中,ConcurrentHashMap 引入了 CAS(Compare and Swap)技術(shù),用于實(shí)現(xiàn)一個(gè)更加高效的并發(fā)控制機(jī)制。CAS 是一種無鎖機(jī)制,可以避免線程爭(zhēng)搶鎖的情況。
我們以 put 操作為例,來看一下 ConcurrentHashMap 的實(shí)現(xiàn)過程:
- 首先計(jì)算 key 的哈希值;
- 根據(jù)哈希值找到對(duì)應(yīng)的 Segment;
- 獲取 Segment 對(duì)應(yīng)的鎖;
- 如果還沒有元素,就直接插入到 Segment 中;
- 如果已經(jīng)存在元素,就循環(huán)比較 key 是否相等;
- 如果 key 已經(jīng)存在,就根據(jù)要求更新 value;
- 如果 key 不存在,就插入新的元素(鏈表或者紅黑樹)。
上述操作中,步驟 2 到 3 相當(dāng)于加了一個(gè)悲觀鎖,在整個(gè)哈希表上加鎖,如果只有一個(gè) Segment,效果與 Hashtable 類似;如果存在多個(gè) Segment,效果就相當(dāng)于使用了分段鎖機(jī)制,提高了并發(fā)訪問性能。
四、使用場(chǎng)景案例
1. 高并發(fā)的計(jì)數(shù)器
ConcurrentHashMap 可以用來實(shí)現(xiàn)高并發(fā)的計(jì)數(shù)器,例如記錄網(wǎng)站訪問量、接口調(diào)用次數(shù)等。具體地,我們可以使用 ConcurrentHashMap 的 compute 方法來實(shí)現(xiàn)計(jì)數(shù)操作,如下所示:
import java.util.concurrent.ConcurrentHashMap;
public class Counter {
private final ConcurrentHashMap<String, Integer> map;
public Counter() {
this.map = new ConcurrentHashMap<>();
}
public void increase(String key) {
map.compute(key, (k, v) -> (v == null) ? 1 : v + 1);
}
public int get(String key) {
return map.getOrDefault(key, 0);
}
}在上述代碼中,我們創(chuàng)建了一個(gè) Counter 類,使用 ConcurrentHashMap 存儲(chǔ)計(jì)數(shù)器數(shù)據(jù)。具體地,我們使用 compute 方法實(shí)現(xiàn)對(duì)計(jì)數(shù)器的增加操作,如果 key 不存在則新建一個(gè)值為 1 的計(jì)數(shù)器;否則將其遞增 1。通過 get 方法可以獲取指定 key 對(duì)應(yīng)的計(jì)數(shù)器值。
2. 線程池任務(wù)管理
ConcurrentHashMap 還可以用來實(shí)現(xiàn)線程池任務(wù)的管理,例如記錄每個(gè)任務(wù)的執(zhí)行狀態(tài)、結(jié)果等信息。具體地,我們可以將一個(gè) ConcurrentHashMap 實(shí)例作為任務(wù)管理器,在任務(wù)執(zhí)行前將任務(wù)信息添加到該管理器中,然后再在任務(wù)完成后更新對(duì)應(yīng)的信息,如下所示:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TaskManager {
private final ConcurrentHashMap<String, TaskInfo> tasks;
public TaskManager() {
this.tasks = new ConcurrentHashMap<>();
}
public void addTask(String taskId, Runnable task) {
// 添加任務(wù)信息
tasks.put(taskId, new TaskInfo());
// 提交任務(wù)到線程池
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(() -> {
try {
// 執(zhí)行任務(wù)
task.run();
// 更新任務(wù)狀態(tài)和結(jié)果
TaskInfo info = tasks.get(taskId);
info.setStatus(TaskStatus.COMPLETED);
info.setResult("Task completed successfully!");
} catch (Exception ex) {
// 更新任務(wù)狀態(tài)和結(jié)果
TaskInfo info = tasks.get(taskId);
info.setStatus(TaskStatus.FAILED);
info.setResult(ex.getMessage());
}
});
// 關(guān)閉線程池
executor.shutdown();
}
public TaskInfo getTaskInfo(String taskId) {
return tasks.getOrDefault(taskId, new TaskInfo());
}
}
enum TaskStatus {
NEW,
RUNNING,
COMPLETED,
FAILED;
}
class TaskInfo {
private TaskStatus status;
private String result;
public TaskInfo() {
this.status = TaskStatus.NEW;
this.result = "";
}
public TaskStatus getStatus() {
return status;
}
public void setStatus(TaskStatus status) {
this.status = status;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
}在上述代碼中,我們創(chuàng)建了一個(gè) TaskManager 類,使用 ConcurrentHashMap 存儲(chǔ)任務(wù)信息。具體地,我們定義了一個(gè) TaskInfo 類來表示任務(wù)信息,其中包括任務(wù)狀態(tài)和結(jié)果兩個(gè)屬性。在添加任務(wù)時(shí),我們新建一個(gè) TaskInfo 實(shí)例并添加到 ConcurrentHashMap 中,然后在異步執(zhí)行任務(wù)的線程中更新其狀態(tài)和結(jié)果;同時(shí)我們使用 ExecutorService 來管理并發(fā)執(zhí)行的任務(wù)。通過 getTaskInfo 方法可以獲取指定 taskId 對(duì)應(yīng)的任務(wù)信息。
3. 緩存管理器
ConcurrentHashMap 還可以用來實(shí)現(xiàn)緩存管理器,例如存儲(chǔ)經(jīng)常使用的業(yè)務(wù)數(shù)據(jù)、系統(tǒng)配置等信息,從而避免頻繁的數(shù)據(jù)庫(kù)查詢或網(wǎng)絡(luò)請(qǐng)求。具體地,我們可以使用 ConcurrentHashMap 存儲(chǔ)緩存數(shù)據(jù),并設(shè)定緩存過期時(shí)間,如下所示:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CacheManager<K, V> {
private final Map<K, CacheEntry<V>> cache;
public CacheManager() {
this.cache = new ConcurrentHashMap<>();
}
public void put(K key, V value, long ttl) {
// 添加緩存項(xiàng),同時(shí)記錄當(dāng)前時(shí)間戳和緩存生存時(shí)間
CacheEntry<V> entry = new CacheEntry<>(value, System.currentTimeMillis(), ttl);
cache.put(key, entry);
}
public V get(K key) {
// 獲取緩存項(xiàng)和其緩存生存時(shí)間
CacheEntry<V> entry = cache.get(key);
// 檢查緩存項(xiàng)是否過期,如果過期則刪除緩存項(xiàng)并返回 null
if (entry != null && !entry.isExpired()) {
return entry.getValue();
} else {
cache.remove(key);
return null;
}
}
static class CacheEntry<V> {
private final V value;
private final long timestamp;
private final long ttl;
public CacheEntry(V value, long timestamp, long ttl) {
this.value = value;
this.timestamp = timestamp;
this.ttl = ttl;
}
public V getValue() {
return value;
}
public boolean isExpired() {
return System.currentTimeMillis() - timestamp > ttl;
}
}
}在上述代碼中,我們創(chuàng)建了一個(gè) CacheManager 類,使用 ConcurrentHashMap 存儲(chǔ)緩存數(shù)據(jù)。具體地,我們定義了一個(gè) CacheEntry 類來表示緩存項(xiàng),其中包括值、時(shí)間戳和緩存生存時(shí)間三個(gè)屬性。在添加緩存項(xiàng)時(shí),我們新建一個(gè) CacheEntry 實(shí)例并添加到 ConcurrentHashMap 中,然后在獲取緩存項(xiàng)時(shí)檢查其是否過期;如果未過期則返回其值,否則刪除緩存項(xiàng)并返回 null。
以上就是ConcurrentHashMap 原理及使用詳解的詳細(xì)內(nèi)容,更多關(guān)于ConcurrentHashMap 原理及用法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺談java中math類中三種取整函數(shù)的區(qū)別
下面小編就為大家?guī)硪黄獪\談java中math類中三種取整函數(shù)的區(qū)別。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11
深入Java Robot實(shí)現(xiàn)控制鼠標(biāo)和鍵盤的方法詳解
本篇文章是對(duì)Java中用Robot實(shí)現(xiàn)控制鼠標(biāo)和鍵盤的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
SpringBoot項(xiàng)目中改變web服務(wù)的路徑的兩種方案
這篇文章主要介紹了SpringBoot項(xiàng)目中改變web服務(wù)的路徑的兩種方案,通過代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-08-08
java 正則,object中兩個(gè)方法的使用(詳解)
下面小編就為大家?guī)硪黄猨ava 正則,object中兩個(gè)方法的使用(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08
用Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的布隆過濾器
這篇文章主要介紹了用Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的布隆過濾器,布隆過濾器是1970年由布隆提出的,它實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù),布隆過濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中,需要的朋友可以參考下2023-12-12
Spring中BeanFactory?FactoryBean和ObjectFactory的三種的區(qū)別
關(guān)于FactoryBean?和?BeanFactory的對(duì)比文章比較多,但是對(duì)ObjectFactory的描述就比較少,今天我們對(duì)比下這三種的區(qū)別,感興趣的朋友跟隨小編一起看看吧2023-01-01
Spring mvc Controller和RestFul原理解析
這篇文章主要介紹了Spring mvc Controller和RestFul原理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03

