什么是ConcurrentHashMap?
????ConcurrentHashMap(Concurrent:并存的,同時(shí)發(fā)生的;)
????ConcurrentHashMap是Java中的一個(gè)線程安全的哈希表實(shí)現(xiàn),它可以在多線程環(huán)境下高效地進(jìn)行并發(fā)操作。
為什么有ConcurrentHashMap?
????HashMap線程不安全,在多線程操作下可能會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)亂
和HashMap區(qū)別
示例代碼對(duì)比
????使用HashMap和ConcurrentHashMap分別實(shí)現(xiàn)以下需求。
????????用30個(gè)線程向?qū)嵗龅膍ap中插入key,value。每一次插入后,把map打印出來(lái)(for循環(huán)中sout)
????????key為for循環(huán)中的i值
????????value:使用UUID
public class HashMapUnsafeTest {
public static void main(String[] args) throws InterruptedException {
//演示HashMap
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
String key = String.valueOf(i);
new Thread(() -> {
//向集合添加內(nèi)容
map.put(key, UUID.randomUUID().toString().substring(0, 8));
//從集合中獲取內(nèi)容
System.out.println(map);
}, "").start();
}
}
}
public class ConcurrentHashMapSafe {
public static void main(String[] args) throws InterruptedException {
//演示ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
String key = String.valueOf(i);
new Thread(() -> {
//向集合添加內(nèi)容
map.put(key, UUID.randomUUID().toString().substring(0, 8));
//從集合中獲取內(nèi)容
System.out.println(map);
}, "").start();
}
}
}
????多個(gè)線程同時(shí)對(duì)同一個(gè)集合進(jìn)行增刪操作導(dǎo)致錯(cuò)誤
JDK7和JDK8中ConcurrentHashMap整體架構(gòu)的區(qū)別
JDK7中
JDK8中
使用到的數(shù)據(jù)結(jié)構(gòu):數(shù)組、單向鏈表、紅黑樹
其中涉及到幾個(gè)核心的參數(shù)
// 最大容量,2^30
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默認(rèn)長(zhǎng)度
private static final int DEFAULT_CAPACITY = 16;
//遺留問(wèn)題:為什么樹化條件是8而取消樹化條件卻是6呢?
// 鏈表樹化條件-是根據(jù)線程競(jìng)爭(zhēng)情況和紅黑樹的操作成本進(jìn)行設(shè)計(jì)的。
static final int TREEIFY_THRESHOLD = 8;
// 取消樹化條件-為了避免過(guò)度的樹化,防止內(nèi)存占用過(guò)高。
static final int UNTREEIFY_THRESHOLD = 6; //鏈表結(jié)構(gòu)中,每個(gè)節(jié)點(diǎn)只需要存儲(chǔ)指向下一個(gè)節(jié)點(diǎn)的指針,而不需要存儲(chǔ)節(jié)點(diǎn)的值。因此,鏈表只需要存儲(chǔ)節(jié)點(diǎn)的引用,占用較少的內(nèi)存空間。樹結(jié)構(gòu)中每個(gè)節(jié)點(diǎn)需要存儲(chǔ)節(jié)點(diǎn)的值以及指向子節(jié)點(diǎn)的指針。
????核心為Hash表,存在Hash沖突問(wèn)題
????使用鏈?zhǔn)酱鎯?chǔ)方式解決沖突,沖突較多時(shí),導(dǎo)致鏈表過(guò)長(zhǎng),查詢效率較低,在JDK1.8中引入紅黑樹機(jī)制;
????當(dāng)數(shù)組長(zhǎng)度大于64,且鏈表長(zhǎng)度大于等于8時(shí),單項(xiàng)鏈表轉(zhuǎn)為紅黑樹
????當(dāng)鏈表長(zhǎng)度小于6時(shí),紅黑樹會(huì)退化為單向鏈表
ConcurrentHashMap的基本功能
????本質(zhì)上是一個(gè)HashMap,功能和HashMap是一樣的
????但ConcurrentHashMap在HashMap基礎(chǔ)上提供了并發(fā)安全的實(shí)現(xiàn),主要通過(guò)對(duì)node節(jié)點(diǎn)加鎖實(shí)現(xiàn),來(lái)保證對(duì)數(shù)據(jù)更新的安全性
鎖粒度變小
在性能方面的優(yōu)化
????在JDK1.8中鎖的粒度是數(shù)組中的某一個(gè)節(jié)點(diǎn),在JDK1.7中鎖定的是一個(gè)Segment,鎖的范圍更大
????保證線程安全機(jī)制:
????????JDK7采用segment的分段鎖機(jī)制實(shí)現(xiàn)線程安全,其中segment繼承自ReentrantLock。
????????JDK8采用CAS(讀)+Synchronized(寫)保證線程安全。
????鎖的粒度:原來(lái)是對(duì)需要進(jìn)行數(shù)據(jù)操作的Segment加鎖,JDK8調(diào)整為對(duì)每個(gè)數(shù)組元素加鎖(Node)。
????鏈表轉(zhuǎn)化為紅黑樹:定位結(jié)點(diǎn)的hash算法簡(jiǎn)化會(huì)帶來(lái)弊端,Hash沖突加劇,因此在鏈表節(jié)點(diǎn)數(shù)量大于8時(shí),會(huì)將鏈表轉(zhuǎn)化為紅黑樹進(jìn)行存儲(chǔ)。
使用到的技術(shù)-CAS
概念說(shuō)明
CAS的全稱是:比較并交換(Compare And Swap)。在CAS中,有這樣三個(gè)值:
????V:要更新的變量(var)
????E:預(yù)期值(expected)
????N:新值(new)
比較并交換的過(guò)程如下:
????判斷V是否等于E,如果等于,將V的值設(shè)置為N;如果不等,說(shuō)明已經(jīng)有其它線程更新了V,則當(dāng)前線程放棄更新,什么都不做。
舉例說(shuō)明
????如果有一個(gè)多個(gè)線程共享的變量i原本等于5,我現(xiàn)在在線程A中,想把它設(shè)置為新的值6;
我們使用CAS來(lái)做這個(gè)事情;
????首先我們用i去與5對(duì)比,發(fā)現(xiàn)它等于5,說(shuō)明沒(méi)有被其它線程改過(guò),那我就把它設(shè)置為新的值6,此次CAS成功,i的值被設(shè)置成了6;
????如果不等于5,說(shuō)明i被其它線程改過(guò)了(比如現(xiàn)在i的值為2),那么我就什么也不做,此次CAS失敗,i的值仍然為2。
底層原理
????unsafe類——以下是類中涉及到的三個(gè)方法用來(lái)實(shí)現(xiàn)CAS效果的,這三個(gè)方法都是由native進(jìn)行修飾的。具體的實(shí)現(xiàn)是由C++寫的。
代碼演示
沒(méi)有使用CAS的代碼
package com.example.threadpool.CAS;
public class NoCASDemo {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
//線程一
Thread thread1= new Thread(() -> {
for (int i=0; i<10000;i++){
counter++;
}
});
//線程二
Thread thread2= new Thread(() -> {
for (int i=0; i<10000;i++){
counter++;
}
});
//執(zhí)行線程
thread1.start();
thread2.start();
//等待執(zhí)行完線程1和2
thread1.join();
thread2.join();
System.out.println("查看counter的總數(shù)"+counter);
}
}
使用CAS的代碼
package com.example.threadpool.CAS;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
//線程一
Thread thread1= new Thread(() -> {
for (int i=0; i<10000;i++){
increment();
}
});
//線程二
Thread thread2= new Thread(() -> {
for (int i=0; i<10000;i++){
increment();
}
});
//執(zhí)行線程
thread1.start();
thread2.start();
//等待執(zhí)行完線程1和2
thread1.join();
thread2.join();
System.out.println("查看counter的總數(shù)"+counter.get());
}
public static void increment() {
int currentValue;
int newValue;
do {
//獲取counter對(duì)象的value值
currentValue = counter.get();
//將counter對(duì)象的value值加1
newValue = currentValue + 1;
} while (!counter.compareAndSet(currentValue, newValue));
}
}
總結(jié)
????總的來(lái)說(shuō),ConcurrentHashMap是Java中線程安全的哈希表實(shí)現(xiàn),它通過(guò)使用鎖分段技術(shù)來(lái)提供高效的并發(fā)性能。相比于Hashtable,ConcurrentHashMap在多線程環(huán)境下能夠更好地支持高并發(fā)讀寫操作。
????使用ConcurrentHashMap可以在多線程環(huán)境下安全地進(jìn)行數(shù)據(jù)操作,而無(wú)需手動(dòng)加鎖。它通過(guò)將整個(gè)數(shù)據(jù)結(jié)構(gòu)分成多個(gè)段來(lái)實(shí)現(xiàn)并發(fā)性能的提升,不同的線程可以同時(shí)訪問(wèn)不同的段,從而減少了線程之間的競(jìng)爭(zhēng)。
????ConcurrentHashMap的設(shè)計(jì)考慮了線程安全和性能的平衡。它提供了一些有用的方法,如putIfAbsent()、replace()等,可以方便地進(jìn)行原子性的操作。此外,ConcurrentHashMap還支持遍歷操作,可以通過(guò)迭代器安全地遍歷其中的元素。
????需要注意的是,雖然ConcurrentHashMap是線程安全的,但并不保證對(duì)于單個(gè)操作的原子性。如果需要進(jìn)行復(fù)合操作,仍然需要額外的同步措施。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-632881.html
????總的來(lái)說(shuō),ConcurrentHashMap是一個(gè)強(qiáng)大的線程安全的哈希表實(shí)現(xiàn),適用于多線程環(huán)境下的高并發(fā)讀寫操作。它提供了高效的并發(fā)性能,可以提升系統(tǒng)的吞吐量和響應(yīng)速度。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-632881.html
到了這里,關(guān)于Java-多線程-深入理解ConcurrentHashMap的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!